From 47fd695dfdc84c6748768f219603e2a793dc439d Mon Sep 17 00:00:00 2001 From: Martin Sirringhaus Date: Tue, 10 Feb 2026 16:34:03 +0100 Subject: [PATCH 1/4] Implement a dialog for setting new device PIN --- credentialsd-common/src/client.rs | 2 + credentialsd-common/src/model.rs | 16 +- credentialsd-ui/data/resources/ui/window.ui | 85 ++++++++++ credentialsd-ui/po/credentialsd-ui.pot | 71 +++++--- credentialsd-ui/po/de_DE.po | 76 ++++++--- credentialsd-ui/po/en_US.po | 71 +++++--- credentialsd-ui/src/client.rs | 16 ++ credentialsd-ui/src/dbus.rs | 2 + credentialsd-ui/src/gui/view_model/gtk/mod.rs | 19 ++- .../src/gui/view_model/gtk/window.rs | 56 +++++++ credentialsd-ui/src/gui/view_model/mod.rs | 86 ++++++---- credentialsd/src/credential_service/hybrid.rs | 2 + credentialsd/src/credential_service/mod.rs | 22 +++ credentialsd/src/credential_service/nfc.rs | 9 ++ credentialsd/src/credential_service/usb.rs | 9 ++ credentialsd/src/dbus/flow_control.rs | 152 ++++++++++++++++++ credentialsd/src/model.rs | 2 + 17 files changed, 600 insertions(+), 96 deletions(-) diff --git a/credentialsd-common/src/client.rs b/credentialsd-common/src/client.rs index 1bff01d..2f9790d 100644 --- a/credentialsd-common/src/client.rs +++ b/credentialsd-common/src/client.rs @@ -22,6 +22,8 @@ pub trait FlowController { Output = Result + Send + 'static>>, ()>, > + Send; fn enter_client_pin(&mut self, pin: String) -> impl Future> + Send; + fn set_usb_device_pin(&mut self, pin: String) -> impl Future> + Send; + fn set_nfc_device_pin(&mut self, pin: String) -> impl Future> + Send; fn select_credential( &self, credential_id: String, diff --git a/credentialsd-common/src/model.rs b/credentialsd-common/src/model.rs index 7c2519b..9a5de6c 100644 --- a/credentialsd-common/src/model.rs +++ b/credentialsd-common/src/model.rs @@ -120,6 +120,20 @@ pub struct RequestingParty { pub origin: String, } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum ViewUpdateFailure { + GeneralFailure(String), + PinNotSet(String), +} + +impl ViewUpdateFailure { + pub fn into_string(self) -> String { + match self { + ViewUpdateFailure::GeneralFailure(msg) | ViewUpdateFailure::PinNotSet(msg) => msg, + } + } +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub enum ViewUpdate { SetTitle((String, String)), @@ -142,7 +156,7 @@ pub enum ViewUpdate { Completed, Cancelled, - Failed(String), + Failed(ViewUpdateFailure), } #[derive(Clone, Debug, Default)] diff --git a/credentialsd-ui/data/resources/ui/window.ui b/credentialsd-ui/data/resources/ui/window.ui index 2fe6d17..5959785 100644 --- a/credentialsd-ui/data/resources/ui/window.ui +++ b/credentialsd-ui/data/resources/ui/window.ui @@ -208,6 +208,66 @@ + + + set_new_pin + Set a PIN + + + vertical + + + + Please choose a new PIN for your device. + true + + + + + + New PIN + + + + + + + Confirm PIN + + + + + + + end + 6 + + + Close + + + + + + Continue + + + CredentialsUiWindow + + + + + + + + + + + + + completed @@ -244,6 +304,31 @@ Something went wrong while retrieving a credential. Please try again later or use a different authenticator. + + + end + 6 + + + + CredentialsUiWindow + + + + + + Close + + + + + + Set PIN on device + + + + + diff --git a/credentialsd-ui/po/credentialsd-ui.pot b/credentialsd-ui/po/credentialsd-ui.pot index 6f15d5e..51ba835 100644 --- a/credentialsd-ui/po/credentialsd-ui.pot +++ b/credentialsd-ui/po/credentialsd-ui.pot @@ -9,7 +9,7 @@ msgstr "" "Project-Id-Version: credentialsd-ui\n" "Report-Msgid-Bugs-To: \"https://github.com/linux-credentials/credentialsd/" "issues\"\n" -"POT-Creation-Date: 2026-02-03 10:40+0100\n" +"POT-Creation-Date: 2026-02-11 14:31+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -21,7 +21,7 @@ msgstr "" #: data/xyz.iinuwa.credentialsd.CredentialsUi.desktop.in.in:2 #: data/xyz.iinuwa.credentialsd.CredentialsUi.metainfo.xml.in.in:8 -#: src/gui/view_model/gtk/mod.rs:385 +#: src/gui/view_model/gtk/mod.rs:400 msgid "Credential Manager" msgstr "" @@ -105,66 +105,95 @@ msgid "Choose credential" msgstr "" #: data/resources/ui/window.ui:214 +msgid "Set a PIN" +msgstr "" + +#: data/resources/ui/window.ui:221 +msgid "Please choose a new PIN for your device." +msgstr "" + +#: data/resources/ui/window.ui:228 +msgid "New PIN" +msgstr "" + +#: data/resources/ui/window.ui:235 +msgid "Confirm PIN" +msgstr "" + +#: data/resources/ui/window.ui:246 data/resources/ui/window.ui:320 +msgid "Close" +msgstr "" + +#: data/resources/ui/window.ui:252 +msgid "Continue" +msgstr "" + +#: data/resources/ui/window.ui:274 msgid "Complete" msgstr "" -#: data/resources/ui/window.ui:220 +#: data/resources/ui/window.ui:280 msgid "Done!" msgstr "" -#: data/resources/ui/window.ui:231 +#: data/resources/ui/window.ui:291 msgid "Something went wrong." msgstr "" -#: data/resources/ui/window.ui:244 src/gui/view_model/mod.rs:290 +#: data/resources/ui/window.ui:304 src/gui/view_model/mod.rs:310 +#: src/gui/view_model/mod.rs:370 msgid "" "Something went wrong while retrieving a credential. Please try again later " "or use a different authenticator." msgstr "" -#: src/gui/view_model/gtk/mod.rs:147 +#: data/resources/ui/window.ui:326 +msgid "Set PIN on device" +msgstr "" + +#: src/gui/view_model/gtk/mod.rs:154 msgid "Enter your PIN. One attempt remaining." msgid_plural "Enter your PIN. %d attempts remaining." msgstr[0] "" msgstr[1] "" -#: src/gui/view_model/gtk/mod.rs:153 +#: src/gui/view_model/gtk/mod.rs:160 msgid "Enter your PIN." msgstr "" -#: src/gui/view_model/gtk/mod.rs:163 +#: src/gui/view_model/gtk/mod.rs:170 msgid "Touch your device again. One attempt remaining." msgid_plural "Touch your device again. %d attempts remaining." msgstr[0] "" msgstr[1] "" -#: src/gui/view_model/gtk/mod.rs:169 +#: src/gui/view_model/gtk/mod.rs:176 msgid "Touch your device." msgstr "" -#: src/gui/view_model/gtk/mod.rs:174 +#: src/gui/view_model/gtk/mod.rs:181 msgid "Touch your device" msgstr "" -#: src/gui/view_model/gtk/mod.rs:177 +#: src/gui/view_model/gtk/mod.rs:184 msgid "Scan the QR code with your device to begin authentication." msgstr "" -#: src/gui/view_model/gtk/mod.rs:187 +#: src/gui/view_model/gtk/mod.rs:194 msgid "" "Connecting to your device. Make sure both devices are near each other and " "have Bluetooth enabled." msgstr "" -#: src/gui/view_model/gtk/mod.rs:195 +#: src/gui/view_model/gtk/mod.rs:202 msgid "Device connected. Follow the instructions on your device" msgstr "" -#: src/gui/view_model/gtk/mod.rs:321 +#: src/gui/view_model/gtk/mod.rs:332 msgid "Insert your security key." msgstr "" -#: src/gui/view_model/gtk/mod.rs:340 +#: src/gui/view_model/gtk/mod.rs:351 msgid "Multiple devices found. Please select with which to proceed." msgstr "" @@ -226,30 +255,30 @@ msgid "" "to sign in to \"%s1\". Only proceed if you trust this process." msgstr "" -#: src/gui/view_model/mod.rs:227 +#: src/gui/view_model/mod.rs:244 msgid "Failed to select credential from device." msgstr "" -#: src/gui/view_model/mod.rs:281 +#: src/gui/view_model/mod.rs:298 src/gui/view_model/mod.rs:358 msgid "No matching credentials found on this authenticator." msgstr "" -#: src/gui/view_model/mod.rs:284 +#: src/gui/view_model/mod.rs:302 src/gui/view_model/mod.rs:362 msgid "" "No more PIN attempts allowed. Try removing your device and plugging it back " "in." msgstr "" -#: src/gui/view_model/mod.rs:287 +#: src/gui/view_model/mod.rs:306 src/gui/view_model/mod.rs:366 msgid "" "This server requires your device to have additional protection like a PIN, " "which is not set. Please set a PIN for this device and try again." msgstr "" -#: src/gui/view_model/mod.rs:293 +#: src/gui/view_model/mod.rs:315 src/gui/view_model/mod.rs:375 msgid "This credential is already registered on this authenticator." msgstr "" -#: src/gui/view_model/mod.rs:395 +#: src/gui/view_model/mod.rs:424 msgid "Something went wrong. Try again later or use a different authenticator." msgstr "" diff --git a/credentialsd-ui/po/de_DE.po b/credentialsd-ui/po/de_DE.po index 1a846e1..17c0dd2 100644 --- a/credentialsd-ui/po/de_DE.po +++ b/credentialsd-ui/po/de_DE.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \"https://github.com/linux-credentials/credentialsd/" "issues\"\n" -"POT-Creation-Date: 2026-02-03 10:40+0100\n" +"POT-Creation-Date: 2026-02-11 14:31+0100\n" "PO-Revision-Date: 2025-10-10 14:45+0200\n" "Last-Translator: Martin Sirringhaus \n" "Language: de_DE\n" @@ -14,7 +14,7 @@ msgstr "" #: data/xyz.iinuwa.credentialsd.CredentialsUi.desktop.in.in:2 #: data/xyz.iinuwa.credentialsd.CredentialsUi.metainfo.xml.in.in:8 -#: src/gui/view_model/gtk/mod.rs:385 +#: src/gui/view_model/gtk/mod.rs:400 msgid "Credential Manager" msgstr "Zugangsdatenmanager" @@ -93,18 +93,43 @@ msgid "Choose credential" msgstr "Wählen Sie Zugangsdaten aus" #: data/resources/ui/window.ui:214 +msgid "Set a PIN" +msgstr "Neue PIN festlegen" + +#: data/resources/ui/window.ui:221 +msgid "Please choose a new PIN for your device." +msgstr "Bitte geben Sie eine neue PIN für das Gerät ein" + +#: data/resources/ui/window.ui:228 +msgid "New PIN" +msgstr "Neue PIN" + +#: data/resources/ui/window.ui:235 +msgid "Confirm PIN" +msgstr "Neue PIN bestätigen" + +#: data/resources/ui/window.ui:246 data/resources/ui/window.ui:320 +msgid "Close" +msgstr "Schließen" + +#: data/resources/ui/window.ui:252 +msgid "Continue" +msgstr " Weiter" + +#: data/resources/ui/window.ui:274 msgid "Complete" msgstr "Abgeschlossen" -#: data/resources/ui/window.ui:220 +#: data/resources/ui/window.ui:280 msgid "Done!" msgstr "Fertig!" -#: data/resources/ui/window.ui:231 +#: data/resources/ui/window.ui:291 msgid "Something went wrong." msgstr "Etwas ist schief gegangen." -#: data/resources/ui/window.ui:244 src/gui/view_model/mod.rs:290 +#: data/resources/ui/window.ui:304 src/gui/view_model/mod.rs:310 +#: src/gui/view_model/mod.rs:370 msgid "" "Something went wrong while retrieving a credential. Please try again later " "or use a different authenticator." @@ -112,37 +137,41 @@ msgstr "" "Beim Abrufen Ihrer Zugangsdaten ist ein Fehler aufgetreten. Versuchen Sie es " "später wieder, oder verwenden Sie einen anderen Security-Token." -#: src/gui/view_model/gtk/mod.rs:147 +#: data/resources/ui/window.ui:326 +msgid "Set PIN on device" +msgstr "Geräte-PIN festlegen" + +#: src/gui/view_model/gtk/mod.rs:154 #, fuzzy msgid "Enter your PIN. One attempt remaining." msgid_plural "Enter your PIN. %d attempts remaining." msgstr[0] "Geben Sie Ihren PIN ein. Sie haben nur noch einen Versuch." msgstr[1] "Geben Sie Ihren PIN ein. Sie haben noch %d Versuche." -#: src/gui/view_model/gtk/mod.rs:153 +#: src/gui/view_model/gtk/mod.rs:160 msgid "Enter your PIN." msgstr "Geben Sie Ihren PIN ein." -#: src/gui/view_model/gtk/mod.rs:163 +#: src/gui/view_model/gtk/mod.rs:170 msgid "Touch your device again. One attempt remaining." msgid_plural "Touch your device again. %d attempts remaining." msgstr[0] "Berühren Sie Ihr Gerät. Sie haben nur noch einen Versuch." msgstr[1] "Berühren Sie nochmal Ihr Gerät. Sie haben nur noch %d Versuche." -#: src/gui/view_model/gtk/mod.rs:169 +#: src/gui/view_model/gtk/mod.rs:176 msgid "Touch your device." msgstr "Berühren Sie Ihr Gerät." -#: src/gui/view_model/gtk/mod.rs:174 +#: src/gui/view_model/gtk/mod.rs:181 msgid "Touch your device" msgstr "Berühren Sie Ihr Gerät." -#: src/gui/view_model/gtk/mod.rs:177 +#: src/gui/view_model/gtk/mod.rs:184 msgid "Scan the QR code with your device to begin authentication." msgstr "" "Scannen Sie den QR code mit ihrem Gerät um die Authentifizierung zu beginnen." -#: src/gui/view_model/gtk/mod.rs:187 +#: src/gui/view_model/gtk/mod.rs:194 msgid "" "Connecting to your device. Make sure both devices are near each other and " "have Bluetooth enabled." @@ -150,15 +179,15 @@ msgstr "" "Verbindung zu Ihrem Gerät wird aufgebaut. Stellen Sie sicher, dass beide " "Geräte nah beieinander sind und Bluetooth aktiviert haben." -#: src/gui/view_model/gtk/mod.rs:195 +#: src/gui/view_model/gtk/mod.rs:202 msgid "Device connected. Follow the instructions on your device" msgstr "Verbindung hergestellt. Folgen Sie den Anweisungen auf Ihrem Gerät." -#: src/gui/view_model/gtk/mod.rs:321 +#: src/gui/view_model/gtk/mod.rs:332 msgid "Insert your security key." msgstr "Stecken Sie Ihren Security-Token ein." -#: src/gui/view_model/gtk/mod.rs:340 +#: src/gui/view_model/gtk/mod.rs:351 msgid "Multiple devices found. Please select with which to proceed." msgstr "Mehrere Geräte gefunden. Bitte wählen Sie einen aus, um fortzufahren." @@ -226,15 +255,15 @@ msgstr "" "abrufen, um Sie bei \"%s1\" anzumelden. Fahren Sie nur fort, wenn Sie diesem " "Prozess vertrauen." -#: src/gui/view_model/mod.rs:227 +#: src/gui/view_model/mod.rs:244 msgid "Failed to select credential from device." msgstr "Zugangsdaten vom Gerät konnten nicht ausgewählt werden." -#: src/gui/view_model/mod.rs:281 +#: src/gui/view_model/mod.rs:298 src/gui/view_model/mod.rs:358 msgid "No matching credentials found on this authenticator." msgstr "Keine passenden Zugangsdaten auf diesem Gerät gefunden." -#: src/gui/view_model/mod.rs:284 +#: src/gui/view_model/mod.rs:302 src/gui/view_model/mod.rs:362 msgid "" "No more PIN attempts allowed. Try removing your device and plugging it back " "in." @@ -242,19 +271,20 @@ msgstr "" "Keine weiteren PIN-Eingaben erlaubt. Versuchen Sie ihr Gerät aus- und wieder " "einzustecken." -#: src/gui/view_model/mod.rs:287 +#: src/gui/view_model/mod.rs:306 src/gui/view_model/mod.rs:366 msgid "" "This server requires your device to have additional protection like a PIN, " "which is not set. Please set a PIN for this device and try again." msgstr "" -"Für diesen Server benötigt ihr Gerät eine zusätzliche Absicherung, z.B. einen PIN. " -"Bitte setzen Sie einen PIN für ihr Gerät und versuchen Sie es erneut." +"Für diesen Server benötigt ihr Gerät eine zusätzliche Absicherung, z.B. " +"einen PIN. Bitte setzen Sie einen PIN für ihr Gerät und versuchen Sie es " +"erneut." -#: src/gui/view_model/mod.rs:293 +#: src/gui/view_model/mod.rs:315 src/gui/view_model/mod.rs:375 msgid "This credential is already registered on this authenticator." msgstr "Diese Zugangsdaten sind bereits auf diesem Gerät registriert." -#: src/gui/view_model/mod.rs:395 +#: src/gui/view_model/mod.rs:424 msgid "Something went wrong. Try again later or use a different authenticator." msgstr "" "Es ist ein Fehler aufgetreten. Bitte versuchen Sie es später noch einmal, " diff --git a/credentialsd-ui/po/en_US.po b/credentialsd-ui/po/en_US.po index bdbfce9..e41b9d3 100644 --- a/credentialsd-ui/po/en_US.po +++ b/credentialsd-ui/po/en_US.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \"https://github.com/linux-credentials/credentialsd/" "issues\"\n" -"POT-Creation-Date: 2026-02-03 10:40+0100\n" +"POT-Creation-Date: 2026-02-11 14:31+0100\n" "PO-Revision-Date: 2025-10-10 14:45+0200\n" "Last-Translator: Martin Sirringhaus \n" "Language: en_US\n" @@ -13,7 +13,7 @@ msgstr "" #: data/xyz.iinuwa.credentialsd.CredentialsUi.desktop.in.in:2 #: data/xyz.iinuwa.credentialsd.CredentialsUi.metainfo.xml.in.in:8 -#: src/gui/view_model/gtk/mod.rs:385 +#: src/gui/view_model/gtk/mod.rs:400 msgid "Credential Manager" msgstr "Credential Manager" @@ -94,18 +94,43 @@ msgid "Choose credential" msgstr "Choose credential" #: data/resources/ui/window.ui:214 +msgid "Set a PIN" +msgstr "Set a PIN" + +#: data/resources/ui/window.ui:221 +msgid "Please choose a new PIN for your device." +msgstr "Please choose a new PIN for your device." + +#: data/resources/ui/window.ui:228 +msgid "New PIN" +msgstr "New PIN" + +#: data/resources/ui/window.ui:235 +msgid "Confirm PIN" +msgstr "Confirm PIN" + +#: data/resources/ui/window.ui:246 data/resources/ui/window.ui:320 +msgid "Close" +msgstr "Close" + +#: data/resources/ui/window.ui:252 +msgid "Continue" +msgstr "Continue" + +#: data/resources/ui/window.ui:274 msgid "Complete" msgstr "Complete" -#: data/resources/ui/window.ui:220 +#: data/resources/ui/window.ui:280 msgid "Done!" msgstr "Done!" -#: data/resources/ui/window.ui:231 +#: data/resources/ui/window.ui:291 msgid "Something went wrong." msgstr "Something went wrong." -#: data/resources/ui/window.ui:244 src/gui/view_model/mod.rs:290 +#: data/resources/ui/window.ui:304 src/gui/view_model/mod.rs:310 +#: src/gui/view_model/mod.rs:370 msgid "" "Something went wrong while retrieving a credential. Please try again later " "or use a different authenticator." @@ -113,35 +138,39 @@ msgstr "" "Something went wrong while retrieving a credential. Please try again later " "or use a different authenticator." -#: src/gui/view_model/gtk/mod.rs:147 +#: data/resources/ui/window.ui:326 +msgid "Set PIN on device" +msgstr "Set PIN on device" + +#: src/gui/view_model/gtk/mod.rs:154 msgid "Enter your PIN. One attempt remaining." msgid_plural "Enter your PIN. %d attempts remaining." msgstr[0] "Enter your PIN. One attempt remaining." msgstr[1] "Enter your PIN. %d attempts remaining." -#: src/gui/view_model/gtk/mod.rs:153 +#: src/gui/view_model/gtk/mod.rs:160 msgid "Enter your PIN." msgstr "Enter your PIN." -#: src/gui/view_model/gtk/mod.rs:163 +#: src/gui/view_model/gtk/mod.rs:170 msgid "Touch your device again. One attempt remaining." msgid_plural "Touch your device again. %d attempts remaining." msgstr[0] "Touch your device again. One attempt remaining." msgstr[1] "Touch your device again. %d attempts remaining." -#: src/gui/view_model/gtk/mod.rs:169 +#: src/gui/view_model/gtk/mod.rs:176 msgid "Touch your device." msgstr "Touch your device." -#: src/gui/view_model/gtk/mod.rs:174 +#: src/gui/view_model/gtk/mod.rs:181 msgid "Touch your device" msgstr "Touch your device" -#: src/gui/view_model/gtk/mod.rs:177 +#: src/gui/view_model/gtk/mod.rs:184 msgid "Scan the QR code with your device to begin authentication." msgstr "Scan the QR code with your device to begin authentication." -#: src/gui/view_model/gtk/mod.rs:187 +#: src/gui/view_model/gtk/mod.rs:194 msgid "" "Connecting to your device. Make sure both devices are near each other and " "have Bluetooth enabled." @@ -149,15 +178,15 @@ msgstr "" "Connecting to your device. Make sure both devices are near each other and " "have Bluetooth enabled." -#: src/gui/view_model/gtk/mod.rs:195 +#: src/gui/view_model/gtk/mod.rs:202 msgid "Device connected. Follow the instructions on your device" msgstr "Device connected. Follow the instructions on your device" -#: src/gui/view_model/gtk/mod.rs:321 +#: src/gui/view_model/gtk/mod.rs:332 msgid "Insert your security key." msgstr "Insert your security key." -#: src/gui/view_model/gtk/mod.rs:340 +#: src/gui/view_model/gtk/mod.rs:351 msgid "Multiple devices found. Please select with which to proceed." msgstr "Multiple devices found. Please select with which to proceed." @@ -223,15 +252,15 @@ msgstr "" "\"%s2\" (process ID: %i1, binary: %s3) is asking to use a credential " "to sign in to \"%s1\". Only proceed if you trust this process." -#: src/gui/view_model/mod.rs:227 +#: src/gui/view_model/mod.rs:244 msgid "Failed to select credential from device." msgstr "Failed to select credential from device." -#: src/gui/view_model/mod.rs:281 +#: src/gui/view_model/mod.rs:298 src/gui/view_model/mod.rs:358 msgid "No matching credentials found on this authenticator." msgstr "No matching credentials found on this authenticator." -#: src/gui/view_model/mod.rs:284 +#: src/gui/view_model/mod.rs:302 src/gui/view_model/mod.rs:362 msgid "" "No more PIN attempts allowed. Try removing your device and plugging it back " "in." @@ -239,7 +268,7 @@ msgstr "" "No more PIN attempts allowed. Try removing your device and plugging it back " "in." -#: src/gui/view_model/mod.rs:287 +#: src/gui/view_model/mod.rs:306 src/gui/view_model/mod.rs:366 msgid "" "This server requires your device to have additional protection like a PIN, " "which is not set. Please set a PIN for this device and try again." @@ -247,11 +276,11 @@ msgstr "" "This server requires your device to have additional protection like a PIN, " "which is not set. Please set a PIN for this device and try again." -#: src/gui/view_model/mod.rs:293 +#: src/gui/view_model/mod.rs:315 src/gui/view_model/mod.rs:375 msgid "This credential is already registered on this authenticator." msgstr "This credential is already registered on this authenticator." -#: src/gui/view_model/mod.rs:395 +#: src/gui/view_model/mod.rs:424 msgid "Something went wrong. Try again later or use a different authenticator." msgstr "" "Something went wrong. Try again later or use a different authenticator." diff --git a/credentialsd-ui/src/client.rs b/credentialsd-ui/src/client.rs index 0f2184d..b5cddfa 100644 --- a/credentialsd-ui/src/client.rs +++ b/credentialsd-ui/src/client.rs @@ -97,6 +97,22 @@ impl FlowController for DbusCredentialClient { .map_err(|err| tracing::error!("Failed to send PIN to authenticator: {err}")) } + async fn set_usb_device_pin(&mut self, pin: String) -> std::result::Result<(), ()> { + self.proxy() + .await? + .set_usb_device_pin(pin) + .await + .map_err(|err| tracing::error!("Failed to set new PIN for authenticator: {err}")) + } + + async fn set_nfc_device_pin(&mut self, pin: String) -> std::result::Result<(), ()> { + self.proxy() + .await? + .set_nfc_device_pin(pin) + .await + .map_err(|err| tracing::error!("Failed to set new PIN for authenticator: {err}")) + } + async fn select_credential(&self, credential_id: String) -> std::result::Result<(), ()> { self.proxy() .await? diff --git a/credentialsd-ui/src/dbus.rs b/credentialsd-ui/src/dbus.rs index 9ab511b..05a0fad 100644 --- a/credentialsd-ui/src/dbus.rs +++ b/credentialsd-ui/src/dbus.rs @@ -26,6 +26,8 @@ pub trait FlowControlService { async fn select_credential(&self, credential_id: String) -> fdo::Result<()>; async fn cancel_request(&self, request_id: RequestId) -> fdo::Result<()>; + async fn set_usb_device_pin(&self, pin: String) -> fdo::Result<()>; + async fn set_nfc_device_pin(&self, pin: String) -> fdo::Result<()>; #[zbus(signal)] async fn state_changed(update: BackgroundEvent) -> zbus::Result<()>; } diff --git a/credentialsd-ui/src/gui/view_model/gtk/mod.rs b/credentialsd-ui/src/gui/view_model/gtk/mod.rs index f82afcb..7e2222f 100644 --- a/credentialsd-ui/src/gui/view_model/gtk/mod.rs +++ b/credentialsd-ui/src/gui/view_model/gtk/mod.rs @@ -4,6 +4,7 @@ pub mod device; mod window; use async_std::channel::{Receiver, Sender}; +use credentialsd_common::model::ViewUpdateFailure; use credentialsd_common::server::WindowHandle; use gettextrs::{LocaleCategory, gettext, ngettext}; use glib::clone; @@ -77,6 +78,12 @@ mod imp { #[property(get, set)] pub qr_spinner_visible: RefCell, + + #[property(get, set)] + pub start_setting_new_pin_visible: RefCell, + + #[property(get, set)] + pub pin_fields_match: RefCell, } // The central trait for subclassing a GObject @@ -200,11 +207,15 @@ impl ViewModel { view_model.set_qr_spinner_visible(false); view_model.set_completed(true); } - ViewUpdate::Failed(error_msg) => { + ViewUpdate::Failed(error) => { view_model.set_qr_spinner_visible(false); view_model.set_failed(true); + view_model.set_start_setting_new_pin_visible(matches!( + &error, + ViewUpdateFailure::PinNotSet(_) + )); // These are already gettext messages - view_model.set_prompt(error_msg); + view_model.set_prompt(error.into_string()); } ViewUpdate::Cancelled => { view_model.set_state(ModelState::Cancelled) @@ -345,6 +356,10 @@ impl ViewModel { self.send_event(ViewEvent::PinEntered(pin)).await; } + pub async fn send_set_new_device_pin(&self, pin: String) { + self.send_event(ViewEvent::SetNewDevicePin(pin)).await; + } + fn draw_qr_code(&self, qr_data: &str) -> Texture { let qr_code = QrCode::new(qr_data).expect("QR code to be valid"); let svg_xml = qr_code.render::().build(); diff --git a/credentialsd-ui/src/gui/view_model/gtk/window.rs b/credentialsd-ui/src/gui/view_model/gtk/window.rs index 642ca73..5946c4c 100644 --- a/credentialsd-ui/src/gui/view_model/gtk/window.rs +++ b/credentialsd-ui/src/gui/view_model/gtk/window.rs @@ -34,6 +34,15 @@ mod imp { #[template_child] pub usb_nfc_pin_entry: TemplateChild, + #[template_child] + pub new_pin_primary_entry: TemplateChild, + + #[template_child] + pub new_pin_confirm_entry: TemplateChild, + + #[template_child] + pub new_pin_btn_continue: TemplateChild, + #[template_child] pub qr_code_pic: TemplateChild, } @@ -53,6 +62,42 @@ mod imp { } )); } + + #[template_callback] + fn handle_start_setting_new_pin(&self) { + let view_model = &self.view_model.borrow(); + let view_model = view_model.as_ref().unwrap(); + // This triggers visibility of the new pin stackpage + view_model.set_pin_fields_match(false); + } + + #[template_callback] + fn handle_setting_pin_change(&self) { + let pin1 = self.new_pin_primary_entry.text(); + let pin2 = self.new_pin_confirm_entry.text(); + let is_valid = !pin1.is_empty() && pin1 == pin2; + // Unlock Button if both entries match (and are non-empty) + self.new_pin_btn_continue.set_sensitive(is_valid); + } + + #[template_callback] + fn handle_close_window(&self) { + self.close_request(); + } + + #[template_callback] + fn handle_commit_new_pin(&self) { + let view_model = &self.view_model.borrow(); + let view_model = view_model.as_ref().unwrap(); + let pin = self.new_pin_primary_entry.text().to_string(); + glib::spawn_future_local(clone!( + #[weak] + view_model, + async move { + view_model.send_set_new_device_pin(pin).await; + } + )); + } } impl Default for CredentialsUiWindow { @@ -64,6 +109,9 @@ mod imp { stack: TemplateChild::default(), usb_nfc_pin_entry: TemplateChild::default(), qr_code_pic: TemplateChild::default(), + new_pin_primary_entry: TemplateChild::default(), + new_pin_confirm_entry: TemplateChild::default(), + new_pin_btn_continue: TemplateChild::default(), } } } @@ -204,6 +252,14 @@ impl CredentialsUiWindow { stack.set_visible_child_name("choose_credential"); } )); + + view_model.connect_pin_fields_match_notify(clone!( + #[weak] + stack, + move |_vm| { + stack.set_visible_child_name("set_new_pin"); + } + )); } fn save_window_size(&self) -> Result<(), glib::BoolError> { diff --git a/credentialsd-ui/src/gui/view_model/mod.rs b/credentialsd-ui/src/gui/view_model/mod.rs index d3113ef..1de4371 100644 --- a/credentialsd-ui/src/gui/view_model/mod.rs +++ b/credentialsd-ui/src/gui/view_model/mod.rs @@ -17,7 +17,7 @@ use credentialsd_common::{ client::FlowController, model::{ BackgroundEvent, Credential, Device, Error, HybridState, NfcState, Operation, Transport, - UsbState, ViewUpdate, + UsbState, ViewUpdate, ViewUpdateFailure, }, }; @@ -213,6 +213,23 @@ impl ViewModel { error!("Failed to send pin to device"); } } + Event::View(ViewEvent::SetNewDevicePin(pin)) => { + if let Some(device) = &self.selected_device { + let mut cred_service = self.flow_controller.lock().await; + let resp = match device.transport { + Transport::Usb => cred_service.set_usb_device_pin(pin).await, + Transport::Nfc => cred_service.set_nfc_device_pin(pin).await, + _ => { + error!("Setting new pin is not supported for this transport!"); + Err(()) + } + }; + + if resp.is_err() { + error!("Failed to send new Pin to device"); + } + } + } Event::View(ViewEvent::CredentialSelected(cred_id)) => { println!( "Credential selected: {:?}. Current Device: {:?}", @@ -229,8 +246,8 @@ impl ViewModel { { tracing::error!("Failed to select credential from device."); self.tx_update - .send(ViewUpdate::Failed(gettext( - "Failed to select credential from device.", + .send(ViewUpdate::Failed(ViewUpdateFailure::GeneralFailure( + gettext("Failed to select credential from device."), ))) .await .unwrap(); @@ -283,21 +300,27 @@ impl ViewModel { // TODO: Provide more specific error messages using the wrapped Error. UsbState::Failed(err) => { let error_msg = match err { - Error::NoCredentials => { - gettext("No matching credentials found on this authenticator.") + Error::NoCredentials => ViewUpdateFailure::GeneralFailure(gettext( + "No matching credentials found on this authenticator.", + )), + Error::PinAttemptsExhausted => { + ViewUpdateFailure::GeneralFailure(gettext( + "No more PIN attempts allowed. Try removing your device and plugging it back in.", + )) } - Error::PinAttemptsExhausted => gettext( - "No more PIN attempts allowed. Try removing your device and plugging it back in.", - ), - Error::PinNotSet => gettext( + Error::PinNotSet => ViewUpdateFailure::PinNotSet(gettext( "This server requires your device to have additional protection like a PIN, which is not set. Please set a PIN for this device and try again.", - ), - Error::AuthenticatorError | Error::Internal(_) => gettext( - "Something went wrong while retrieving a credential. Please try again later or use a different authenticator.", - ), - Error::CredentialExcluded => gettext( - "This credential is already registered on this authenticator.", - ), + )), + Error::AuthenticatorError | Error::Internal(_) => { + ViewUpdateFailure::GeneralFailure(gettext( + "Something went wrong while retrieving a credential. Please try again later or use a different authenticator.", + )) + } + Error::CredentialExcluded => { + ViewUpdateFailure::GeneralFailure(gettext( + "This credential is already registered on this authenticator.", + )) + } }; self.tx_update .send(ViewUpdate::Failed(error_msg)) @@ -336,23 +359,29 @@ impl ViewModel { } // TODO: Provide more specific error messages using the wrapped Error. NfcState::Failed(err) => { - let error_msg = String::from(match err { - Error::NoCredentials => { - "No matching credentials found on this authenticator." - } + let error_msg = match err { + Error::NoCredentials => ViewUpdateFailure::GeneralFailure(gettext( + "No matching credentials found on this authenticator.", + )), Error::PinAttemptsExhausted => { - "No more PIN attempts allowed. Try removing your device and plugging it back in." - } - Error::PinNotSet => { - "This server requires your device to have additional protection like a PIN, which is not set. Please set a PIN for this device and try again." + ViewUpdateFailure::GeneralFailure(gettext( + "No more PIN attempts allowed. Try removing your device and plugging it back in.", + )) } + Error::PinNotSet => ViewUpdateFailure::PinNotSet(gettext( + "This server requires your device to have additional protection like a PIN, which is not set. Please set a PIN for this device and try again.", + )), Error::AuthenticatorError | Error::Internal(_) => { - "Something went wrong while retrieving a credential. Please try again later or use a different authenticator." + ViewUpdateFailure::GeneralFailure(gettext( + "Something went wrong while retrieving a credential. Please try again later or use a different authenticator.", + )) } Error::CredentialExcluded => { - "This credential is already registered on this authenticator." + ViewUpdateFailure::GeneralFailure(gettext( + "This credential is already registered on this authenticator.", + )) } - }); + }; self.tx_update .send(ViewUpdate::Failed(error_msg)) .await @@ -398,7 +427,7 @@ impl ViewModel { } HybridState::Failed => { self.hybrid_qr_code_data = None; - self.tx_update.send(ViewUpdate::Failed(gettext("Something went wrong. Try again later or use a different authenticator."))).await.unwrap(); + self.tx_update.send(ViewUpdate::Failed(ViewUpdateFailure::GeneralFailure(gettext("Something went wrong. Try again later or use a different authenticator.")))).await.unwrap(); } }; } /* @@ -417,6 +446,7 @@ pub enum ViewEvent { DeviceSelected(String), CredentialSelected(String), PinEntered(String), + SetNewDevicePin(String), UserCancelled, } diff --git a/credentialsd/src/credential_service/hybrid.rs b/credentialsd/src/credential_service/hybrid.rs index 85cb6d5..e86ede9 100644 --- a/credentialsd/src/credential_service/hybrid.rs +++ b/credentialsd/src/credential_service/hybrid.rs @@ -49,6 +49,7 @@ impl HybridHandler for InternalHybridHandler { CredentialRequest::GetPublicKeyCredentialRequest(_) => { QrCodeOperationHint::GetAssertionRequest } + CredentialRequest::SetDevicePinRequest(_) => unimplemented!(), }; let mut device = CableQrCodeDevice::new_transient(hint); let qr_code = device.qr_code.to_string(); @@ -123,6 +124,7 @@ impl HybridHandler for InternalHybridHandler { } }; } + CredentialRequest::SetDevicePinRequest(_) => unimplemented!(), } }; let terminal_state = match response { diff --git a/credentialsd/src/credential_service/mod.rs b/credentialsd/src/credential_service/mod.rs index 09b593b..c9ee87d 100644 --- a/credentialsd/src/credential_service/mod.rs +++ b/credentialsd/src/credential_service/mod.rs @@ -126,10 +126,12 @@ impl< let operation = match &request { CredentialRequest::CreatePublicKeyCredentialRequest(_) => Operation::Create, CredentialRequest::GetPublicKeyCredentialRequest(_) => Operation::Get, + CredentialRequest::SetDevicePinRequest(_) => unimplemented!(), }; let rp_id = match &request { CredentialRequest::CreatePublicKeyCredentialRequest(r) => r.relying_party.id.clone(), CredentialRequest::GetPublicKeyCredentialRequest(r) => r.relying_party_id.clone(), + CredentialRequest::SetDevicePinRequest(_) => unimplemented!(), }; let view_request = ViewRequest { operation, @@ -238,6 +240,26 @@ impl< todo!("Handle error when context is not set up.") } } + + pub fn set_usb_device_pin( + &self, + pin: String, + ) -> Pin + Send + 'static>> { + let request = CredentialRequest::SetDevicePinRequest(pin); + let stream = self.usb_handler.start(&request); + let ctx = self.ctx.clone(); + Box::pin(UsbStateStream { inner: stream, ctx }) + } + + pub fn set_nfc_device_pin( + &self, + pin: String, + ) -> Pin + Send + 'static>> { + let request = CredentialRequest::SetDevicePinRequest(pin); + let stream = self.nfc_handler.start(&request); + let ctx = self.ctx.clone(); + Box::pin(NfcStateStream { inner: stream, ctx }) + } } pub struct HybridStateStream { diff --git a/credentialsd/src/credential_service/nfc.rs b/credentialsd/src/credential_service/nfc.rs index 51b8a75..5905c23 100644 --- a/credentialsd/src/credential_service/nfc.rs +++ b/credentialsd/src/credential_service/nfc.rs @@ -5,6 +5,7 @@ use base64::{self, engine::general_purpose::URL_SAFE_NO_PAD, Engine}; use futures_lite::Stream; use libwebauthn::{ ops::webauthn::GetAssertionResponse, + pin::PinManagement, proto::CtapError, transport::{nfc::device::NfcDevice, Channel, Device}, webauthn::{Error as WebAuthnError, WebAuthn}, @@ -141,6 +142,9 @@ impl InProcessNfcHandler { } } }, + Ok(NfcUvMessage::SetPinSuccess) => Ok(NfcStateInternal::Completed( + CredentialResponse::SetDevicePinSuccessRespone, + )), Err(err) => Err(err), }, None => Err(Error::Internal("NFC UV handler channel closed".to_string())), @@ -223,6 +227,10 @@ async fn handle_events( NfcUvMessage::ReceivedCredentials(Box::new(response.into())) }) } + CredentialRequest::SetDevicePinRequest(new_pin) => channel + .change_pin(new_pin.to_string(), Duration::from_secs(300)) + .await + .map(|_| NfcUvMessage::SetPinSuccess), CredentialRequest::GetPublicKeyCredentialRequest(get_cred_request) => channel .webauthn_get_assertion(get_cred_request) .await @@ -511,4 +519,5 @@ enum NfcUvMessage { attempts_left: Option, }, ReceivedCredentials(Box), + SetPinSuccess, } diff --git a/credentialsd/src/credential_service/usb.rs b/credentialsd/src/credential_service/usb.rs index f64c302..8200639 100644 --- a/credentialsd/src/credential_service/usb.rs +++ b/credentialsd/src/credential_service/usb.rs @@ -5,6 +5,7 @@ use base64::{self, engine::general_purpose::URL_SAFE_NO_PAD, Engine}; use futures_lite::Stream; use libwebauthn::{ ops::webauthn::GetAssertionResponse, + pin::PinManagement, proto::CtapError, transport::{ hid::{channel::HidChannelHandle, HidDevice}, @@ -227,6 +228,9 @@ impl InProcessUsbHandler { } } }, + Ok(UsbUvMessage::SetPinSuccess) => Ok(UsbStateInternal::Completed( + CredentialResponse::SetDevicePinSuccessRespone, + )), Err(err) => Err(err), }, None => Err(Error::Internal("USB UV handler channel closed".to_string())), @@ -319,6 +323,10 @@ async fn handle_events( .map(|response| { UsbUvMessage::ReceivedCredentials(Box::new(response.into())) }), + CredentialRequest::SetDevicePinRequest(new_pin) => channel + .change_pin(new_pin.to_string(), Duration::from_secs(300)) + .await + .map(|_| UsbUvMessage::SetPinSuccess), }; match response { Ok(response) => { @@ -624,4 +632,5 @@ enum UsbUvMessage { }, NeedsUserPresence, ReceivedCredentials(Box), + SetPinSuccess, } diff --git a/credentialsd/src/dbus/flow_control.rs b/credentialsd/src/dbus/flow_control.rs index a6ed555..20bfb61 100644 --- a/credentialsd/src/dbus/flow_control.rs +++ b/credentialsd/src/dbus/flow_control.rs @@ -292,6 +292,116 @@ where Ok(()) } + async fn set_usb_device_pin( + &self, + pin: String, + #[zbus(object_server)] object_server: &ObjectServer, + ) -> fdo::Result<()> { + let mut stream = self.svc.lock().await.set_usb_device_pin(pin); + let usb_cred_tx = self.cred_tx.clone(); + let signal_state = self.signal_state.clone(); + let object_server = object_server.clone(); + let task = tokio::spawn(async move { + let interface: zbus::Result>> = + object_server.interface(SERVICE_PATH).await; + + let emitter = match interface { + Ok(ref i) => i.signal_emitter(), + Err(err) => { + tracing::error!("Failed to get connection to D-Bus to send signals: {err}"); + return; + } + }; + while let Some(state) = stream.next().await { + let event = + credentialsd_common::model::BackgroundEvent::UsbStateChanged((&state).into()); + if let Err(err) = send_state_update(emitter, &signal_state, event).await { + tracing::error!("Failed to send state update to UI: {err}"); + break; + }; + match state { + UsbState::NeedsPin { .. } => { + tracing::error!( + "We are setting a PIN, but the device asks us for one. Aborting" + ); + break; + } + UsbState::SelectCredential { cred_tx, .. } => { + // TODO: This is not great. The user potentially already selected a device, + // but we are starting a new request cycle, so they have to select one + // again... But the previous cycle has already ended. + let mut usb_cred_tx = usb_cred_tx.lock().await; + let _ = usb_cred_tx.insert(cred_tx); + } + UsbState::Completed | UsbState::Failed(_) => { + break; + } + _ => {} + }; + } + }) + .abort_handle(); + if let Some(prev_task) = self.usb_event_forwarder_task.lock().await.replace(task) { + prev_task.abort(); + } + Ok(()) + } + + async fn set_nfc_device_pin( + &self, + pin: String, + #[zbus(object_server)] object_server: &ObjectServer, + ) -> fdo::Result<()> { + let mut stream = self.svc.lock().await.set_nfc_device_pin(pin); + let usb_cred_tx = self.cred_tx.clone(); + let signal_state = self.signal_state.clone(); + let object_server = object_server.clone(); + let task = tokio::spawn(async move { + let interface: zbus::Result>> = + object_server.interface(SERVICE_PATH).await; + + let emitter = match interface { + Ok(ref i) => i.signal_emitter(), + Err(err) => { + tracing::error!("Failed to get connection to D-Bus to send signals: {err}"); + return; + } + }; + while let Some(state) = stream.next().await { + let event = + credentialsd_common::model::BackgroundEvent::NfcStateChanged((&state).into()); + if let Err(err) = send_state_update(emitter, &signal_state, event).await { + tracing::error!("Failed to send state update to UI: {err}"); + break; + }; + match state { + NfcState::NeedsPin { .. } => { + tracing::error!( + "We are setting a PIN, but the device asks us for one. Aborting" + ); + break; + } + NfcState::SelectCredential { cred_tx, .. } => { + // TODO: This is not great. The user already selected a device, + // but we are starting a new request cycle, so they have + // to select one again... + let mut usb_cred_tx = usb_cred_tx.lock().await; + let _ = usb_cred_tx.insert(cred_tx); + } + NfcState::Completed | NfcState::Failed(_) => { + break; + } + _ => {} + }; + } + }) + .abort_handle(); + if let Some(prev_task) = self.nfc_event_forwarder_task.lock().await.replace(task) { + prev_task.abort(); + } + Ok(()) + } + async fn select_credential(&self, credential_id: String) -> fdo::Result<()> { if let Some(cred_tx) = self.cred_tx.lock().await.take() { cred_tx.send(credential_id).await.unwrap(); @@ -419,6 +529,8 @@ pub mod test { #[allow(clippy::enum_variant_names)] #[derive(Debug)] pub enum DummyFlowRequest { + SetUsbDevicePin(String), + SetNfcDevicePin(String), EnterClientPin(String), GetDevices, GetHybridCredential, @@ -431,6 +543,7 @@ pub mod test { // intentional for now. #[allow(clippy::enum_variant_names)] pub enum DummyFlowResponse { + SetNewDevicePin(Result<(), ()>), EnterClientPin(Result<(), ()>), GetDevices(Vec), GetHybridCredential, @@ -442,6 +555,9 @@ pub mod test { impl Debug for DummyFlowResponse { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { + Self::SetNewDevicePin(arg0) => { + f.debug_tuple("SetNewDevicePin").field(arg0).finish() + } Self::EnterClientPin(arg0) => f.debug_tuple("EnterClientPin").field(arg0).finish(), Self::GetDevices(arg0) => f.debug_tuple("GetDevices").field(arg0).finish(), Self::GetHybridCredential => f.debug_tuple("GetHybridCredential").finish(), @@ -534,6 +650,26 @@ pub mod test { } } + async fn set_usb_device_pin(&mut self, pin: String) -> Result<(), ()> { + if let Ok(DummyFlowResponse::SetNewDevicePin(Ok(()))) = + self.send(DummyFlowRequest::SetUsbDevicePin(pin)).await + { + Ok(()) + } else { + Err(()) + } + } + + async fn set_nfc_device_pin(&mut self, pin: String) -> Result<(), ()> { + if let Ok(DummyFlowResponse::SetNewDevicePin(Ok(()))) = + self.send(DummyFlowRequest::SetNfcDevicePin(pin)).await + { + Ok(()) + } else { + Err(()) + } + } + async fn select_credential(&self, _credential_id: String) -> Result<(), ()> { todo!(); } @@ -612,6 +748,14 @@ pub mod test { let rsp = self.enter_client_pin(pin).await; DummyFlowResponse::EnterClientPin(rsp) } + DummyFlowRequest::SetUsbDevicePin(pin) => { + let rsp = self.set_usb_device_pin(pin).await; + DummyFlowResponse::SetNewDevicePin(rsp) + } + DummyFlowRequest::SetNfcDevicePin(pin) => { + let rsp = self.set_nfc_device_pin(pin).await; + DummyFlowResponse::SetNewDevicePin(rsp) + } DummyFlowRequest::GetDevices => { let rsp = self.get_available_public_key_devices().await.unwrap(); DummyFlowResponse::GetDevices(rsp) @@ -793,6 +937,14 @@ pub mod test { Ok(()) } + async fn set_usb_device_pin(&self, _pin: String) -> Result<(), ()> { + todo!(); + } + + async fn set_nfc_device_pin(&self, _pin: String) -> Result<(), ()> { + todo!(); + } + async fn select_credential(&self, _credential_id: String) -> Result<(), ()> { todo!(); } diff --git a/credentialsd/src/model.rs b/credentialsd/src/model.rs index cb59df9..ee25750 100644 --- a/credentialsd/src/model.rs +++ b/credentialsd/src/model.rs @@ -6,12 +6,14 @@ use libwebauthn::ops::webauthn::{ pub enum CredentialRequest { CreatePublicKeyCredentialRequest(MakeCredentialRequest), GetPublicKeyCredentialRequest(GetAssertionRequest), + SetDevicePinRequest(String), } #[derive(Clone, Debug)] pub enum CredentialResponse { CreatePublicKeyCredentialResponse(Box), GetPublicKeyCredentialResponse(Box), + SetDevicePinSuccessRespone, } impl CredentialResponse { From e0a944333b7eaad5486e9f0582c6cb6c4520ce54 Mon Sep 17 00:00:00 2001 From: Martin Sirringhaus Date: Thu, 12 Feb 2026 14:33:15 +0100 Subject: [PATCH 2/4] Make it possible to retry setting a PIN, if previous entry does not conform to the PIN policy --- credentialsd-common/src/model.rs | 10 ++++++- credentialsd-common/src/server.rs | 3 ++ credentialsd-ui/po/credentialsd-ui.pot | 28 ++++++++++------- credentialsd-ui/po/de_DE.po | 30 ++++++++++++------- credentialsd-ui/po/en_US.po | 30 ++++++++++++------- credentialsd-ui/src/gui/view_model/gtk/mod.rs | 1 + credentialsd-ui/src/gui/view_model/mod.rs | 10 +++++++ credentialsd/src/credential_service/nfc.rs | 5 +++- credentialsd/src/credential_service/usb.rs | 5 +++- 9 files changed, 86 insertions(+), 36 deletions(-) diff --git a/credentialsd-common/src/model.rs b/credentialsd-common/src/model.rs index 9a5de6c..d5504f5 100644 --- a/credentialsd-common/src/model.rs +++ b/credentialsd-common/src/model.rs @@ -123,13 +123,18 @@ pub struct RequestingParty { #[derive(Debug, Clone, Serialize, Deserialize)] pub enum ViewUpdateFailure { GeneralFailure(String), + /// Request required UV, but it was not set on the device yet PinNotSet(String), + /// User tried to set PIN, but it was too short + PinPolicyViolation(String), } impl ViewUpdateFailure { pub fn into_string(self) -> String { match self { - ViewUpdateFailure::GeneralFailure(msg) | ViewUpdateFailure::PinNotSet(msg) => msg, + ViewUpdateFailure::GeneralFailure(msg) + | ViewUpdateFailure::PinNotSet(msg) + | ViewUpdateFailure::PinPolicyViolation(msg) => msg, } } } @@ -287,6 +292,8 @@ pub enum Error { PinAttemptsExhausted, /// The RP requires user verification, but the device has no PIN/Biometrics set. PinNotSet, + /// The device declined the entered PIN, as it violates the PIN policy (e.g. PIN too short) + PinPolicyViolation, // 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), @@ -299,6 +306,7 @@ impl Display for Error { match self { Self::AuthenticatorError => f.write_str("AuthenticatorError"), Self::PinNotSet => f.write_str("PinNotSet"), + Self::PinPolicyViolation => f.write_str("PinPolicyViolation"), Self::NoCredentials => f.write_str("NoCredentials"), Self::CredentialExcluded => f.write_str("CredentialExcluded"), Self::PinAttemptsExhausted => f.write_str("PinAttemptsExhausted"), diff --git a/credentialsd-common/src/server.rs b/credentialsd-common/src/server.rs index 8096634..93d761c 100644 --- a/credentialsd-common/src/server.rs +++ b/credentialsd-common/src/server.rs @@ -183,6 +183,7 @@ impl TryFrom<&Value<'_>> for crate::model::Error { let err = match err_code { "AuthenticatorError" => crate::model::Error::AuthenticatorError, "PinNotSet" => crate::model::Error::PinNotSet, + "PinPolicyViolation" => crate::model::Error::PinPolicyViolation, "NoCredentials" => crate::model::Error::NoCredentials, "CredentialExcluded" => crate::model::Error::CredentialExcluded, "PinAttemptsExhausted" => crate::model::Error::PinAttemptsExhausted, @@ -401,6 +402,7 @@ impl TryFrom<&Structure<'_>> for crate::model::UsbState { let err = match err_code { "AuthenticatorError" => crate::model::Error::AuthenticatorError, "PinNotSet" => crate::model::Error::PinNotSet, + "PinPolicyViolation" => crate::model::Error::PinPolicyViolation, "NoCredentials" => crate::model::Error::NoCredentials, "CredentialExcluded" => crate::model::Error::CredentialExcluded, "PinAttemptsExhausted" => crate::model::Error::PinAttemptsExhausted, @@ -524,6 +526,7 @@ impl TryFrom<&Structure<'_>> for crate::model::NfcState { let err = match err_code { "AuthenticatorError" => crate::model::Error::AuthenticatorError, "PinNotSet" => crate::model::Error::PinNotSet, + "PinPolicyViolation" => crate::model::Error::PinPolicyViolation, "NoCredentials" => crate::model::Error::NoCredentials, "CredentialExcluded" => crate::model::Error::CredentialExcluded, "PinAttemptsExhausted" => crate::model::Error::PinAttemptsExhausted, diff --git a/credentialsd-ui/po/credentialsd-ui.pot b/credentialsd-ui/po/credentialsd-ui.pot index 51ba835..8e6a1bb 100644 --- a/credentialsd-ui/po/credentialsd-ui.pot +++ b/credentialsd-ui/po/credentialsd-ui.pot @@ -9,7 +9,7 @@ msgstr "" "Project-Id-Version: credentialsd-ui\n" "Report-Msgid-Bugs-To: \"https://github.com/linux-credentials/credentialsd/" "issues\"\n" -"POT-Creation-Date: 2026-02-11 14:31+0100\n" +"POT-Creation-Date: 2026-02-12 14:18+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -21,7 +21,7 @@ msgstr "" #: data/xyz.iinuwa.credentialsd.CredentialsUi.desktop.in.in:2 #: data/xyz.iinuwa.credentialsd.CredentialsUi.metainfo.xml.in.in:8 -#: src/gui/view_model/gtk/mod.rs:400 +#: src/gui/view_model/gtk/mod.rs:401 msgid "Credential Manager" msgstr "" @@ -140,8 +140,8 @@ msgstr "" msgid "Something went wrong." msgstr "" -#: data/resources/ui/window.ui:304 src/gui/view_model/mod.rs:310 -#: src/gui/view_model/mod.rs:370 +#: data/resources/ui/window.ui:304 src/gui/view_model/mod.rs:315 +#: src/gui/view_model/mod.rs:380 msgid "" "Something went wrong while retrieving a credential. Please try again later " "or use a different authenticator." @@ -189,11 +189,11 @@ msgstr "" msgid "Device connected. Follow the instructions on your device" msgstr "" -#: src/gui/view_model/gtk/mod.rs:332 +#: src/gui/view_model/gtk/mod.rs:333 msgid "Insert your security key." msgstr "" -#: src/gui/view_model/gtk/mod.rs:351 +#: src/gui/view_model/gtk/mod.rs:352 msgid "Multiple devices found. Please select with which to proceed." msgstr "" @@ -259,26 +259,32 @@ msgstr "" msgid "Failed to select credential from device." msgstr "" -#: src/gui/view_model/mod.rs:298 src/gui/view_model/mod.rs:358 +#: src/gui/view_model/mod.rs:298 src/gui/view_model/mod.rs:363 msgid "No matching credentials found on this authenticator." msgstr "" -#: src/gui/view_model/mod.rs:302 src/gui/view_model/mod.rs:362 +#: src/gui/view_model/mod.rs:302 src/gui/view_model/mod.rs:367 msgid "" "No more PIN attempts allowed. Try removing your device and plugging it back " "in." msgstr "" -#: src/gui/view_model/mod.rs:306 src/gui/view_model/mod.rs:366 +#: src/gui/view_model/mod.rs:306 src/gui/view_model/mod.rs:371 msgid "" "This server requires your device to have additional protection like a PIN, " "which is not set. Please set a PIN for this device and try again." msgstr "" -#: src/gui/view_model/mod.rs:315 src/gui/view_model/mod.rs:375 +#: src/gui/view_model/mod.rs:310 src/gui/view_model/mod.rs:375 +msgid "" +"The entered PIN violates the PIN-policy of this device (likely too short). " +"Please try again." +msgstr "" + +#: src/gui/view_model/mod.rs:320 src/gui/view_model/mod.rs:385 msgid "This credential is already registered on this authenticator." msgstr "" -#: src/gui/view_model/mod.rs:424 +#: src/gui/view_model/mod.rs:434 msgid "Something went wrong. Try again later or use a different authenticator." msgstr "" diff --git a/credentialsd-ui/po/de_DE.po b/credentialsd-ui/po/de_DE.po index 17c0dd2..0958adf 100644 --- a/credentialsd-ui/po/de_DE.po +++ b/credentialsd-ui/po/de_DE.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \"https://github.com/linux-credentials/credentialsd/" "issues\"\n" -"POT-Creation-Date: 2026-02-11 14:31+0100\n" +"POT-Creation-Date: 2026-02-12 14:18+0100\n" "PO-Revision-Date: 2025-10-10 14:45+0200\n" "Last-Translator: Martin Sirringhaus \n" "Language: de_DE\n" @@ -14,7 +14,7 @@ msgstr "" #: data/xyz.iinuwa.credentialsd.CredentialsUi.desktop.in.in:2 #: data/xyz.iinuwa.credentialsd.CredentialsUi.metainfo.xml.in.in:8 -#: src/gui/view_model/gtk/mod.rs:400 +#: src/gui/view_model/gtk/mod.rs:401 msgid "Credential Manager" msgstr "Zugangsdatenmanager" @@ -128,8 +128,8 @@ msgstr "Fertig!" msgid "Something went wrong." msgstr "Etwas ist schief gegangen." -#: data/resources/ui/window.ui:304 src/gui/view_model/mod.rs:310 -#: src/gui/view_model/mod.rs:370 +#: data/resources/ui/window.ui:304 src/gui/view_model/mod.rs:315 +#: src/gui/view_model/mod.rs:380 msgid "" "Something went wrong while retrieving a credential. Please try again later " "or use a different authenticator." @@ -183,11 +183,11 @@ msgstr "" msgid "Device connected. Follow the instructions on your device" msgstr "Verbindung hergestellt. Folgen Sie den Anweisungen auf Ihrem Gerät." -#: src/gui/view_model/gtk/mod.rs:332 +#: src/gui/view_model/gtk/mod.rs:333 msgid "Insert your security key." msgstr "Stecken Sie Ihren Security-Token ein." -#: src/gui/view_model/gtk/mod.rs:351 +#: src/gui/view_model/gtk/mod.rs:352 msgid "Multiple devices found. Please select with which to proceed." msgstr "Mehrere Geräte gefunden. Bitte wählen Sie einen aus, um fortzufahren." @@ -259,11 +259,11 @@ msgstr "" msgid "Failed to select credential from device." msgstr "Zugangsdaten vom Gerät konnten nicht ausgewählt werden." -#: src/gui/view_model/mod.rs:298 src/gui/view_model/mod.rs:358 +#: src/gui/view_model/mod.rs:298 src/gui/view_model/mod.rs:363 msgid "No matching credentials found on this authenticator." msgstr "Keine passenden Zugangsdaten auf diesem Gerät gefunden." -#: src/gui/view_model/mod.rs:302 src/gui/view_model/mod.rs:362 +#: src/gui/view_model/mod.rs:302 src/gui/view_model/mod.rs:367 msgid "" "No more PIN attempts allowed. Try removing your device and plugging it back " "in." @@ -271,7 +271,7 @@ msgstr "" "Keine weiteren PIN-Eingaben erlaubt. Versuchen Sie ihr Gerät aus- und wieder " "einzustecken." -#: src/gui/view_model/mod.rs:306 src/gui/view_model/mod.rs:366 +#: src/gui/view_model/mod.rs:306 src/gui/view_model/mod.rs:371 msgid "" "This server requires your device to have additional protection like a PIN, " "which is not set. Please set a PIN for this device and try again." @@ -280,11 +280,19 @@ msgstr "" "einen PIN. Bitte setzen Sie einen PIN für ihr Gerät und versuchen Sie es " "erneut." -#: src/gui/view_model/mod.rs:315 src/gui/view_model/mod.rs:375 +#: src/gui/view_model/mod.rs:310 src/gui/view_model/mod.rs:375 +msgid "" +"The entered PIN violates the PIN-policy of this device (likely too short). " +"Please try again." +msgstr "" +"Der eingegebene PIN entspricht nicht den PIN-Bestimmungen des Geräts (z.B. " +"zu kurz). Bitte versuchen Sie es erneut." + +#: src/gui/view_model/mod.rs:320 src/gui/view_model/mod.rs:385 msgid "This credential is already registered on this authenticator." msgstr "Diese Zugangsdaten sind bereits auf diesem Gerät registriert." -#: src/gui/view_model/mod.rs:424 +#: src/gui/view_model/mod.rs:434 msgid "Something went wrong. Try again later or use a different authenticator." msgstr "" "Es ist ein Fehler aufgetreten. Bitte versuchen Sie es später noch einmal, " diff --git a/credentialsd-ui/po/en_US.po b/credentialsd-ui/po/en_US.po index e41b9d3..89e8ee0 100644 --- a/credentialsd-ui/po/en_US.po +++ b/credentialsd-ui/po/en_US.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \"https://github.com/linux-credentials/credentialsd/" "issues\"\n" -"POT-Creation-Date: 2026-02-11 14:31+0100\n" +"POT-Creation-Date: 2026-02-12 14:18+0100\n" "PO-Revision-Date: 2025-10-10 14:45+0200\n" "Last-Translator: Martin Sirringhaus \n" "Language: en_US\n" @@ -13,7 +13,7 @@ msgstr "" #: data/xyz.iinuwa.credentialsd.CredentialsUi.desktop.in.in:2 #: data/xyz.iinuwa.credentialsd.CredentialsUi.metainfo.xml.in.in:8 -#: src/gui/view_model/gtk/mod.rs:400 +#: src/gui/view_model/gtk/mod.rs:401 msgid "Credential Manager" msgstr "Credential Manager" @@ -129,8 +129,8 @@ msgstr "Done!" msgid "Something went wrong." msgstr "Something went wrong." -#: data/resources/ui/window.ui:304 src/gui/view_model/mod.rs:310 -#: src/gui/view_model/mod.rs:370 +#: data/resources/ui/window.ui:304 src/gui/view_model/mod.rs:315 +#: src/gui/view_model/mod.rs:380 msgid "" "Something went wrong while retrieving a credential. Please try again later " "or use a different authenticator." @@ -182,11 +182,11 @@ msgstr "" msgid "Device connected. Follow the instructions on your device" msgstr "Device connected. Follow the instructions on your device" -#: src/gui/view_model/gtk/mod.rs:332 +#: src/gui/view_model/gtk/mod.rs:333 msgid "Insert your security key." msgstr "Insert your security key." -#: src/gui/view_model/gtk/mod.rs:351 +#: src/gui/view_model/gtk/mod.rs:352 msgid "Multiple devices found. Please select with which to proceed." msgstr "Multiple devices found. Please select with which to proceed." @@ -256,11 +256,11 @@ msgstr "" msgid "Failed to select credential from device." msgstr "Failed to select credential from device." -#: src/gui/view_model/mod.rs:298 src/gui/view_model/mod.rs:358 +#: src/gui/view_model/mod.rs:298 src/gui/view_model/mod.rs:363 msgid "No matching credentials found on this authenticator." msgstr "No matching credentials found on this authenticator." -#: src/gui/view_model/mod.rs:302 src/gui/view_model/mod.rs:362 +#: src/gui/view_model/mod.rs:302 src/gui/view_model/mod.rs:367 msgid "" "No more PIN attempts allowed. Try removing your device and plugging it back " "in." @@ -268,7 +268,7 @@ msgstr "" "No more PIN attempts allowed. Try removing your device and plugging it back " "in." -#: src/gui/view_model/mod.rs:306 src/gui/view_model/mod.rs:366 +#: src/gui/view_model/mod.rs:306 src/gui/view_model/mod.rs:371 msgid "" "This server requires your device to have additional protection like a PIN, " "which is not set. Please set a PIN for this device and try again." @@ -276,11 +276,19 @@ msgstr "" "This server requires your device to have additional protection like a PIN, " "which is not set. Please set a PIN for this device and try again." -#: src/gui/view_model/mod.rs:315 src/gui/view_model/mod.rs:375 +#: src/gui/view_model/mod.rs:310 src/gui/view_model/mod.rs:375 +msgid "" +"The entered PIN violates the PIN-policy of this device (likely too short). " +"Please try again." +msgstr "" +"The entered PIN violates the PIN-policy of this device (likely too short). " +"Please try again." + +#: src/gui/view_model/mod.rs:320 src/gui/view_model/mod.rs:385 msgid "This credential is already registered on this authenticator." msgstr "This credential is already registered on this authenticator." -#: src/gui/view_model/mod.rs:424 +#: src/gui/view_model/mod.rs:434 msgid "Something went wrong. Try again later or use a different authenticator." msgstr "" "Something went wrong. Try again later or use a different authenticator." diff --git a/credentialsd-ui/src/gui/view_model/gtk/mod.rs b/credentialsd-ui/src/gui/view_model/gtk/mod.rs index 7e2222f..ac6480a 100644 --- a/credentialsd-ui/src/gui/view_model/gtk/mod.rs +++ b/credentialsd-ui/src/gui/view_model/gtk/mod.rs @@ -213,6 +213,7 @@ impl ViewModel { view_model.set_start_setting_new_pin_visible(matches!( &error, ViewUpdateFailure::PinNotSet(_) + | ViewUpdateFailure::PinPolicyViolation(_) )); // These are already gettext messages view_model.set_prompt(error.into_string()); diff --git a/credentialsd-ui/src/gui/view_model/mod.rs b/credentialsd-ui/src/gui/view_model/mod.rs index 1de4371..e19b52b 100644 --- a/credentialsd-ui/src/gui/view_model/mod.rs +++ b/credentialsd-ui/src/gui/view_model/mod.rs @@ -311,6 +311,11 @@ impl ViewModel { Error::PinNotSet => ViewUpdateFailure::PinNotSet(gettext( "This server requires your device to have additional protection like a PIN, which is not set. Please set a PIN for this device and try again.", )), + Error::PinPolicyViolation => { + ViewUpdateFailure::PinPolicyViolation(gettext( + "The entered PIN violates the PIN-policy of this device (likely too short). Please try again.", + )) + } Error::AuthenticatorError | Error::Internal(_) => { ViewUpdateFailure::GeneralFailure(gettext( "Something went wrong while retrieving a credential. Please try again later or use a different authenticator.", @@ -371,6 +376,11 @@ impl ViewModel { Error::PinNotSet => ViewUpdateFailure::PinNotSet(gettext( "This server requires your device to have additional protection like a PIN, which is not set. Please set a PIN for this device and try again.", )), + Error::PinPolicyViolation => { + ViewUpdateFailure::PinPolicyViolation(gettext( + "The entered PIN violates the PIN-policy of this device (likely too short). Please try again.", + )) + } Error::AuthenticatorError | Error::Internal(_) => { ViewUpdateFailure::GeneralFailure(gettext( "Something went wrong while retrieving a credential. Please try again later or use a different authenticator.", diff --git a/credentialsd/src/credential_service/nfc.rs b/credentialsd/src/credential_service/nfc.rs index 5905c23..25fd97a 100644 --- a/credentialsd/src/credential_service/nfc.rs +++ b/credentialsd/src/credential_service/nfc.rs @@ -8,7 +8,7 @@ use libwebauthn::{ pin::PinManagement, proto::CtapError, transport::{nfc::device::NfcDevice, Channel, Device}, - webauthn::{Error as WebAuthnError, WebAuthn}, + webauthn::{Error as WebAuthnError, PlatformError, WebAuthn}, UvUpdate, }; use tokio::sync::broadcast; @@ -261,6 +261,9 @@ async fn handle_events( .map_err(|err| match err { WebAuthnError::Ctap(CtapError::PINAuthBlocked) => Error::PinAttemptsExhausted, WebAuthnError::Ctap(CtapError::PINNotSet) => Error::PinNotSet, + WebAuthnError::Platform(PlatformError::PinTooShort) + | WebAuthnError::Platform(PlatformError::PinTooLong) + | WebAuthnError::Ctap(CtapError::PINPolicyViolation) => Error::PinPolicyViolation, WebAuthnError::Ctap(CtapError::NoCredentials) => Error::NoCredentials, WebAuthnError::Ctap(CtapError::CredentialExcluded) => Error::CredentialExcluded, _ => Error::AuthenticatorError, diff --git a/credentialsd/src/credential_service/usb.rs b/credentialsd/src/credential_service/usb.rs index 8200639..892d1ca 100644 --- a/credentialsd/src/credential_service/usb.rs +++ b/credentialsd/src/credential_service/usb.rs @@ -11,7 +11,7 @@ use libwebauthn::{ hid::{channel::HidChannelHandle, HidDevice}, Channel, Device, }, - webauthn::{Error as WebAuthnError, WebAuthn}, + webauthn::{Error as WebAuthnError, PlatformError, WebAuthn}, UvUpdate, }; use tokio::sync::broadcast; @@ -351,6 +351,9 @@ async fn handle_events( .map_err(|err| match err { WebAuthnError::Ctap(CtapError::PINAuthBlocked) => Error::PinAttemptsExhausted, WebAuthnError::Ctap(CtapError::PINNotSet) => Error::PinNotSet, + WebAuthnError::Platform(PlatformError::PinTooShort) + | WebAuthnError::Platform(PlatformError::PinTooLong) + | WebAuthnError::Ctap(CtapError::PINPolicyViolation) => Error::PinPolicyViolation, WebAuthnError::Ctap(CtapError::NoCredentials) => Error::NoCredentials, WebAuthnError::Ctap(CtapError::CredentialExcluded) => Error::CredentialExcluded, _ => Error::AuthenticatorError, From 6243e5f9054f905f2c0460e37d0a6384c7d365b5 Mon Sep 17 00:00:00 2001 From: Martin Sirringhaus Date: Tue, 17 Feb 2026 12:16:55 +0100 Subject: [PATCH 3/4] Add completed-state to have option to keep window open (not yet working, because of cancel-request intervening) --- credentialsd-common/src/model.rs | 11 ++++++- credentialsd-ui/src/gui/view_model/gtk/mod.rs | 9 ++++-- credentialsd-ui/src/gui/view_model/mod.rs | 29 ++++++++++++++++--- credentialsd/src/dbus/flow_control.rs | 2 +- credentialsd/src/webauthn.rs | 1 + 5 files changed, 44 insertions(+), 8 deletions(-) diff --git a/credentialsd-common/src/model.rs b/credentialsd-common/src/model.rs index d5504f5..f3a72b0 100644 --- a/credentialsd-common/src/model.rs +++ b/credentialsd-common/src/model.rs @@ -40,6 +40,7 @@ pub struct Device { pub enum Operation { Create, Get, + SetDevicePin, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Type)] @@ -139,6 +140,14 @@ impl ViewUpdateFailure { } } +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub enum ViewUpdateSuccess { + /// Success that requires the window to close + CloseWindow, + /// Success that requires the window to stay open + KeepWindowOpen(String), +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub enum ViewUpdate { SetTitle((String, String)), @@ -159,7 +168,7 @@ pub enum ViewUpdate { HybridConnecting, HybridConnected, - Completed, + Completed(ViewUpdateSuccess), Cancelled, Failed(ViewUpdateFailure), } diff --git a/credentialsd-ui/src/gui/view_model/gtk/mod.rs b/credentialsd-ui/src/gui/view_model/gtk/mod.rs index ac6480a..daa1f4e 100644 --- a/credentialsd-ui/src/gui/view_model/gtk/mod.rs +++ b/credentialsd-ui/src/gui/view_model/gtk/mod.rs @@ -4,7 +4,7 @@ pub mod device; mod window; use async_std::channel::{Receiver, Sender}; -use credentialsd_common::model::ViewUpdateFailure; +use credentialsd_common::model::{ViewUpdateFailure, ViewUpdateSuccess}; use credentialsd_common::server::WindowHandle; use gettextrs::{LocaleCategory, gettext, ngettext}; use glib::clone; @@ -203,10 +203,15 @@ impl ViewModel { )); view_model.set_qr_spinner_visible(false); } - ViewUpdate::Completed => { + ViewUpdate::Completed(ViewUpdateSuccess::CloseWindow) => { view_model.set_qr_spinner_visible(false); view_model.set_completed(true); } + ViewUpdate::Completed(ViewUpdateSuccess::KeepWindowOpen(text)) => { + view_model.set_qr_spinner_visible(false); + // These are already gettext messages + view_model.set_prompt(text); + } ViewUpdate::Failed(error) => { view_model.set_qr_spinner_visible(false); view_model.set_failed(true); diff --git a/credentialsd-ui/src/gui/view_model/mod.rs b/credentialsd-ui/src/gui/view_model/mod.rs index e19b52b..5f68f64 100644 --- a/credentialsd-ui/src/gui/view_model/mod.rs +++ b/credentialsd-ui/src/gui/view_model/mod.rs @@ -7,7 +7,7 @@ use async_std::{ channel::{Receiver, Sender}, sync::Mutex as AsyncMutex, }; -use credentialsd_common::model::RequestingApplication; +use credentialsd_common::model::{RequestingApplication, ViewUpdateSuccess}; use credentialsd_common::server::ViewRequest; use gettextrs::gettext; use serde::{Deserialize, Serialize}; @@ -89,6 +89,7 @@ impl ViewModel { // TRANSLATORS: %s1 is the "relying party" (think: domain name) where the request is coming from gettext("Use a passkey for %s1") } + Operation::SetDevicePin => gettext("Setting Pin on device"), } .to_string(); title = title.replace("%s1", &self.rp_id); @@ -108,6 +109,7 @@ impl ViewModel { // TRANSLATORS: %s3 is the absolute path (think: /usr/bin/firefox) of the requesting application gettext("\"%s2\" (process ID: %i1, binary: %s3) is asking to use a credential to sign in to \"%s1\". Only proceed if you trust this process.") } + Operation::SetDevicePin => gettext("Setting Pin on device"), } .to_string(); subtitle = subtitle.replace("%s1", &self.rp_id); @@ -216,6 +218,7 @@ impl ViewModel { Event::View(ViewEvent::SetNewDevicePin(pin)) => { if let Some(device) = &self.selected_device { let mut cred_service = self.flow_controller.lock().await; + self.operation = Operation::SetDevicePin; let resp = match device.transport { Transport::Usb => cred_service.set_usb_device_pin(pin).await, Transport::Nfc => cred_service.set_nfc_device_pin(pin).await, @@ -282,7 +285,15 @@ impl ViewModel { .unwrap(); } UsbState::Completed => { - self.tx_update.send(ViewUpdate::Completed).await.unwrap(); + let t = if matches!(self.operation, Operation::SetDevicePin) { + ViewUpdateSuccess::KeepWindowOpen(gettext( + "Pin successfully set! Please try registering again.", + )) + } else { + ViewUpdateSuccess::CloseWindow + }; + + self.tx_update.send(ViewUpdate::Completed(t)).await.unwrap(); } UsbState::SelectingDevice => { self.tx_update @@ -353,7 +364,14 @@ impl ViewModel { .unwrap(); } NfcState::Completed => { - self.tx_update.send(ViewUpdate::Completed).await.unwrap(); + let t = if matches!(self.operation, Operation::SetDevicePin) { + ViewUpdateSuccess::KeepWindowOpen(gettext( + "Pin successfully set! Please try registering again.", + )) + } else { + ViewUpdateSuccess::CloseWindow + }; + self.tx_update.send(ViewUpdate::Completed(t)).await.unwrap(); } NfcState::Idle | NfcState::Waiting => {} NfcState::SelectingCredential { creds } => { @@ -429,7 +447,10 @@ impl ViewModel { } HybridState::Completed => { self.hybrid_qr_code_data = None; - self.tx_update.send(ViewUpdate::Completed).await.unwrap(); + self.tx_update + .send(ViewUpdate::Completed(ViewUpdateSuccess::CloseWindow)) + .await + .unwrap(); } HybridState::UserCancelled => { self.hybrid_qr_code_data = None; diff --git a/credentialsd/src/dbus/flow_control.rs b/credentialsd/src/dbus/flow_control.rs index 20bfb61..9f7b8ed 100644 --- a/credentialsd/src/dbus/flow_control.rs +++ b/credentialsd/src/dbus/flow_control.rs @@ -326,7 +326,7 @@ where ); break; } - UsbState::SelectCredential { cred_tx, .. } => { + UsbState::SelectingCredential { cred_tx, .. } => { // TODO: This is not great. The user potentially already selected a device, // but we are starting a new request cycle, so they have to select one // again... But the previous cycle has already ended. diff --git a/credentialsd/src/webauthn.rs b/credentialsd/src/webauthn.rs index 905fccf..87c3fa3 100644 --- a/credentialsd/src/webauthn.rs +++ b/credentialsd/src/webauthn.rs @@ -682,6 +682,7 @@ pub fn format_client_data_json( let op_str = match op { Operation::Create => "webauthn.create", Operation::Get => "webauthn.get", + _ => unreachable!(), }; let mut client_data_json = format!( r#"{{"type":"{}","challenge":"{}","origin":"{}""#, From 44e1c23e8e112de59d75c251f149f090bdd0b8bd Mon Sep 17 00:00:00 2001 From: Martin Sirringhaus Date: Fri, 20 Mar 2026 11:47:49 +0100 Subject: [PATCH 4/4] Switch to interactive PinNotSet flow, using devel-branch of libwebauthn --- Cargo.lock | 2 +- credentialsd-common/src/client.rs | 3 +- credentialsd-common/src/model.rs | 77 +++++---- credentialsd-common/src/server.rs | 52 +++++- credentialsd-ui/po/credentialsd-ui.pot | 80 ++++----- credentialsd-ui/po/de_DE.po | 91 +++++----- credentialsd-ui/po/en_US.po | 89 +++++----- credentialsd-ui/src/client.rs | 12 +- credentialsd-ui/src/dbus.rs | 3 +- credentialsd-ui/src/gui/view_model/gtk/mod.rs | 37 +++-- .../src/gui/view_model/gtk/window.rs | 11 ++ credentialsd-ui/src/gui/view_model/mod.rs | 138 +++++----------- credentialsd/Cargo.toml | 2 +- credentialsd/src/credential_service/hybrid.rs | 2 - credentialsd/src/credential_service/mod.rs | 22 --- credentialsd/src/credential_service/nfc.rs | 69 ++++++-- credentialsd/src/credential_service/usb.rs | 69 ++++++-- credentialsd/src/dbus/flow_control.rs | 155 +++--------------- credentialsd/src/model.rs | 2 - 19 files changed, 449 insertions(+), 467 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0626a7b..80f8df2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1921,7 +1921,7 @@ dependencies = [ [[package]] name = "libwebauthn" version = "0.2.2" -source = "git+https://github.com/linux-credentials/libwebauthn.git?rev=80545bff16c4e89a930221e90d3141a76303b84b#80545bff16c4e89a930221e90d3141a76303b84b" +source = "git+https://github.com/msirringhaus/libwebauthn.git?rev=fc41f140f74ea27da1b4c85d3888ae393afcabda#fc41f140f74ea27da1b4c85d3888ae393afcabda" dependencies = [ "aes", "apdu", diff --git a/credentialsd-common/src/client.rs b/credentialsd-common/src/client.rs index 2f9790d..d12163a 100644 --- a/credentialsd-common/src/client.rs +++ b/credentialsd-common/src/client.rs @@ -22,8 +22,7 @@ pub trait FlowController { Output = Result + Send + 'static>>, ()>, > + Send; fn enter_client_pin(&mut self, pin: String) -> impl Future> + Send; - fn set_usb_device_pin(&mut self, pin: String) -> impl Future> + Send; - fn set_nfc_device_pin(&mut self, pin: String) -> impl Future> + Send; + fn set_device_pin(&mut self, pin: String) -> impl Future> + Send; fn select_credential( &self, credential_id: String, diff --git a/credentialsd-common/src/model.rs b/credentialsd-common/src/model.rs index f3a72b0..4b0130e 100644 --- a/credentialsd-common/src/model.rs +++ b/credentialsd-common/src/model.rs @@ -40,7 +40,6 @@ pub struct Device { pub enum Operation { Create, Get, - SetDevicePin, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Type)] @@ -121,33 +120,6 @@ pub struct RequestingParty { pub origin: String, } -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum ViewUpdateFailure { - GeneralFailure(String), - /// Request required UV, but it was not set on the device yet - PinNotSet(String), - /// User tried to set PIN, but it was too short - PinPolicyViolation(String), -} - -impl ViewUpdateFailure { - pub fn into_string(self) -> String { - match self { - ViewUpdateFailure::GeneralFailure(msg) - | ViewUpdateFailure::PinNotSet(msg) - | ViewUpdateFailure::PinPolicyViolation(msg) => msg, - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub enum ViewUpdateSuccess { - /// Success that requires the window to close - CloseWindow, - /// Success that requires the window to stay open - KeepWindowOpen(String), -} - #[derive(Debug, Clone, Serialize, Deserialize)] pub enum ViewUpdate { SetTitle((String, String)), @@ -160,17 +132,48 @@ pub enum ViewUpdate { UsbNeedsPin { attempts_left: Option }, UsbNeedsUserVerification { attempts_left: Option }, UsbNeedsUserPresence, + UsbPinNotSet { error: Option }, NfcNeedsPin { attempts_left: Option }, NfcNeedsUserVerification { attempts_left: Option }, + NfcPinNotSet { error: Option }, HybridNeedsQrCode(String), HybridConnecting, HybridConnected, - Completed(ViewUpdateSuccess), + Completed, Cancelled, - Failed(ViewUpdateFailure), + Failed(String), +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum PinNotSetError { + /// PIN too short + PinTooShort, + /// PIN too long + PinTooLong, + /// PIN violates PinPolicy + PinPolicyViolation, +} + +impl PinNotSetError { + pub fn to_string(&self) -> String { + match self { + PinNotSetError::PinTooShort => String::from("Pin too short"), + PinNotSetError::PinTooLong => String::from("Pin too long"), + PinNotSetError::PinPolicyViolation => String::from("Pin policy violation"), + } + } + + pub fn from_string(error: &str) -> Option { + match error { + "Pin too short" => Some(PinNotSetError::PinTooShort), + "Pin too long" => Some(PinNotSetError::PinTooLong), + "Pin policy violation" => Some(PinNotSetError::PinPolicyViolation), + _ => None, + } + } } #[derive(Clone, Debug, Default)] @@ -220,6 +223,11 @@ pub enum UsbState { attempts_left: Option, }, + /// The device needs the PIN to be entered. + PinNotSet { + error: Option, + }, + /// The device needs on-device user verification. NeedsUserVerification { attempts_left: Option, @@ -259,6 +267,9 @@ pub enum NfcState { /// The device needs the PIN to be entered. NeedsPin { attempts_left: Option }, + /// The device needs the PIN to be entered. + PinNotSet { error: Option }, + /// The device needs on-device user verification. NeedsUserVerification { attempts_left: Option }, @@ -299,10 +310,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, - /// The device declined the entered PIN, as it violates the PIN policy (e.g. PIN too short) - PinPolicyViolation, // 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), @@ -314,8 +321,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::PinPolicyViolation => f.write_str("PinPolicyViolation"), Self::NoCredentials => f.write_str("NoCredentials"), Self::CredentialExcluded => f.write_str("CredentialExcluded"), Self::PinAttemptsExhausted => f.write_str("PinAttemptsExhausted"), diff --git a/credentialsd-common/src/server.rs b/credentialsd-common/src/server.rs index 93d761c..4892cc1 100644 --- a/credentialsd-common/src/server.rs +++ b/credentialsd-common/src/server.rs @@ -11,7 +11,7 @@ use zvariant::{ SerializeDict, Signature, Structure, StructureBuilder, Type, Value, signature::Fields, }; -use crate::model::{BackgroundEvent, Device, Operation, RequestingApplication}; +use crate::model::{BackgroundEvent, Operation, PinNotSetError, RequestingApplication}; const TAG_VALUE_SIGNATURE: &Signature = &Signature::Structure(Fields::Static { fields: &[&Signature::U8, &Signature::Variant], @@ -182,8 +182,6 @@ impl TryFrom<&Value<'_>> for crate::model::Error { let err_code: &str = value.downcast_ref()?; let err = match err_code { "AuthenticatorError" => crate::model::Error::AuthenticatorError, - "PinNotSet" => crate::model::Error::PinNotSet, - "PinPolicyViolation" => crate::model::Error::PinPolicyViolation, "NoCredentials" => crate::model::Error::NoCredentials, "CredentialExcluded" => crate::model::Error::CredentialExcluded, "PinAttemptsExhausted" => crate::model::Error::PinAttemptsExhausted, @@ -347,6 +345,21 @@ impl From<&crate::model::UsbState> for Structure<'_> { let value = Value::<'_>::from(error.to_string()); (0x0A, Some(value)) } + crate::model::UsbState::PinNotSet { error } => { + let value = error.as_ref().map(|x| { + Value::<'_>::from({ + let this = &x; + match this { + PinNotSetError::PinTooShort => String::from("Pin too short"), + PinNotSetError::PinTooLong => String::from("Pin too long"), + PinNotSetError::PinPolicyViolation => { + String::from("Pin policy violation") + } + } + }) + }); + (0x0B, value) + } }; tag_value_to_struct(tag, value) } @@ -401,8 +414,6 @@ impl TryFrom<&Structure<'_>> for crate::model::UsbState { let err_code: &str = value.downcast_ref()?; let err = match err_code { "AuthenticatorError" => crate::model::Error::AuthenticatorError, - "PinNotSet" => crate::model::Error::PinNotSet, - "PinPolicyViolation" => crate::model::Error::PinPolicyViolation, "NoCredentials" => crate::model::Error::NoCredentials, "CredentialExcluded" => crate::model::Error::CredentialExcluded, "PinAttemptsExhausted" => crate::model::Error::PinAttemptsExhausted, @@ -410,6 +421,13 @@ impl TryFrom<&Structure<'_>> for crate::model::UsbState { }; Ok(Self::Failed(err)) } + 0x0B => { + let error = value + .downcast_ref::<&str>() + .ok() + .and_then(PinNotSetError::from_string); + Ok(Self::PinNotSet { error }) + } _ => Err(zvariant::Error::IncorrectType), } } @@ -473,6 +491,21 @@ impl From<&crate::model::NfcState> for Structure<'_> { let value = Value::<'_>::from(error.to_string()); (0x0A, Some(value)) } + crate::model::NfcState::PinNotSet { error } => { + let value = error.as_ref().map(|x| { + Value::<'_>::from({ + let this = &x; + match this { + PinNotSetError::PinTooShort => String::from("Pin too short"), + PinNotSetError::PinTooLong => String::from("Pin too long"), + PinNotSetError::PinPolicyViolation => { + String::from("Pin policy violation") + } + } + }) + }); + (0x0B, value) + } }; tag_value_to_struct(tag, value) } @@ -525,8 +558,6 @@ impl TryFrom<&Structure<'_>> for crate::model::NfcState { let err_code: &str = value.downcast_ref()?; let err = match err_code { "AuthenticatorError" => crate::model::Error::AuthenticatorError, - "PinNotSet" => crate::model::Error::PinNotSet, - "PinPolicyViolation" => crate::model::Error::PinPolicyViolation, "NoCredentials" => crate::model::Error::NoCredentials, "CredentialExcluded" => crate::model::Error::CredentialExcluded, "PinAttemptsExhausted" => crate::model::Error::PinAttemptsExhausted, @@ -534,6 +565,13 @@ impl TryFrom<&Structure<'_>> for crate::model::NfcState { }; Ok(Self::Failed(err)) } + 0x0B => { + let error = value + .downcast_ref::<&str>() + .ok() + .and_then(PinNotSetError::from_string); + Ok(Self::PinNotSet { error }) + } _ => Err(zvariant::Error::IncorrectType), } } diff --git a/credentialsd-ui/po/credentialsd-ui.pot b/credentialsd-ui/po/credentialsd-ui.pot index 8e6a1bb..ca2c703 100644 --- a/credentialsd-ui/po/credentialsd-ui.pot +++ b/credentialsd-ui/po/credentialsd-ui.pot @@ -9,7 +9,7 @@ msgstr "" "Project-Id-Version: credentialsd-ui\n" "Report-Msgid-Bugs-To: \"https://github.com/linux-credentials/credentialsd/" "issues\"\n" -"POT-Creation-Date: 2026-02-12 14:18+0100\n" +"POT-Creation-Date: 2026-03-20 11:40+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -21,7 +21,7 @@ msgstr "" #: data/xyz.iinuwa.credentialsd.CredentialsUi.desktop.in.in:2 #: data/xyz.iinuwa.credentialsd.CredentialsUi.metainfo.xml.in.in:8 -#: src/gui/view_model/gtk/mod.rs:401 +#: src/gui/view_model/gtk/mod.rs:417 msgid "Credential Manager" msgstr "" @@ -57,7 +57,7 @@ msgid "Registering a credential" msgstr "" #. developer_name tag deprecated with Appstream 1.0 -#: data/xyz.iinuwa.credentialsd.CredentialsUi.metainfo.xml.in.in:34 +#: data/xyz.iinuwa.credentialsd.CredentialsUi.metainfo.xml.in.in:35 msgid "Isaiah Inuwa" msgstr "" @@ -140,8 +140,8 @@ msgstr "" msgid "Something went wrong." msgstr "" -#: data/resources/ui/window.ui:304 src/gui/view_model/mod.rs:315 -#: src/gui/view_model/mod.rs:380 +#: data/resources/ui/window.ui:304 src/gui/view_model/mod.rs:305 +#: src/gui/view_model/mod.rs:362 msgid "" "Something went wrong while retrieving a credential. Please try again later " "or use a different authenticator." @@ -151,49 +151,67 @@ msgstr "" msgid "Set PIN on device" msgstr "" -#: src/gui/view_model/gtk/mod.rs:154 +#: src/gui/view_model/gtk/mod.rs:156 msgid "Enter your PIN. One attempt remaining." msgid_plural "Enter your PIN. %d attempts remaining." msgstr[0] "" msgstr[1] "" -#: src/gui/view_model/gtk/mod.rs:160 +#: src/gui/view_model/gtk/mod.rs:162 msgid "Enter your PIN." msgstr "" -#: src/gui/view_model/gtk/mod.rs:170 +#: src/gui/view_model/gtk/mod.rs:172 msgid "Touch your device again. One attempt remaining." msgid_plural "Touch your device again. %d attempts remaining." msgstr[0] "" msgstr[1] "" -#: src/gui/view_model/gtk/mod.rs:176 +#: src/gui/view_model/gtk/mod.rs:178 msgid "Touch your device." msgstr "" -#: src/gui/view_model/gtk/mod.rs:181 +#: src/gui/view_model/gtk/mod.rs:183 msgid "Touch your device" msgstr "" -#: src/gui/view_model/gtk/mod.rs:184 +#: src/gui/view_model/gtk/mod.rs:191 +msgid "" +"This server requires your device to have additional protection like a PIN, " +"which is not set. Please set a PIN for this device and try again." +msgstr "" + +#: src/gui/view_model/gtk/mod.rs:195 +msgid "" +"The entered PIN violates the PIN-policy of this device (likely too short). " +"Please try again." +msgstr "" + +#: src/gui/view_model/gtk/mod.rs:198 +msgid "" +"The entered PIN violates the PIN-policy of this device (PIN too long). " +"Please try again." +msgstr "" + +#: src/gui/view_model/gtk/mod.rs:205 msgid "Scan the QR code with your device to begin authentication." msgstr "" -#: src/gui/view_model/gtk/mod.rs:194 +#: src/gui/view_model/gtk/mod.rs:215 msgid "" "Connecting to your device. Make sure both devices are near each other and " "have Bluetooth enabled." msgstr "" -#: src/gui/view_model/gtk/mod.rs:202 +#: src/gui/view_model/gtk/mod.rs:223 msgid "Device connected. Follow the instructions on your device" msgstr "" -#: src/gui/view_model/gtk/mod.rs:333 +#: src/gui/view_model/gtk/mod.rs:349 msgid "Insert your security key." msgstr "" -#: src/gui/view_model/gtk/mod.rs:352 +#: src/gui/view_model/gtk/mod.rs:368 msgid "Multiple devices found. Please select with which to proceed." msgstr "" @@ -221,17 +239,17 @@ msgstr "" msgid "A security key (USB)" msgstr "" -#: src/gui/view_model/mod.rs:75 +#: src/gui/view_model/mod.rs:70 msgid "unknown application" msgstr "" #. TRANSLATORS: %s1 is the "relying party" (think: domain name) where the request is coming from -#: src/gui/view_model/mod.rs:80 +#: src/gui/view_model/mod.rs:86 msgid "Create a passkey for %s1" msgstr "" #. TRANSLATORS: %s1 is the "relying party" (think: domain name) where the request is coming from -#: src/gui/view_model/mod.rs:84 +#: src/gui/view_model/mod.rs:90 msgid "Use a passkey for %s1" msgstr "" @@ -239,7 +257,7 @@ msgstr "" #. TRANSLATORS: %s2 is the application name (e.g.: firefox) where the request is coming from, must be left untouched to make the name bold #. TRANSLATORS: %i1 is the process ID of the requesting application #. TRANSLATORS: %s3 is the absolute path (think: /usr/bin/firefox) of the requesting application -#: src/gui/view_model/mod.rs:96 +#: src/gui/view_model/mod.rs:102 msgid "" "\"%s2\" (process ID: %i1, binary: %s3) is asking to create a " "credential to register at \"%s1\". Only proceed if you trust this process." @@ -249,42 +267,30 @@ msgstr "" #. TRANSLATORS: %s2 is the application name (e.g.: firefox) where the request is coming from, must be left untouched to make the name bold #. TRANSLATORS: %i1 is the process ID of the requesting application #. TRANSLATORS: %s3 is the absolute path (think: /usr/bin/firefox) of the requesting application -#: src/gui/view_model/mod.rs:103 +#: src/gui/view_model/mod.rs:109 msgid "" "\"%s2\" (process ID: %i1, binary: %s3) is asking to use a credential " "to sign in to \"%s1\". Only proceed if you trust this process." msgstr "" -#: src/gui/view_model/mod.rs:244 +#: src/gui/view_model/mod.rs:239 msgid "Failed to select credential from device." msgstr "" -#: src/gui/view_model/mod.rs:298 src/gui/view_model/mod.rs:363 +#: src/gui/view_model/mod.rs:299 src/gui/view_model/mod.rs:356 msgid "No matching credentials found on this authenticator." msgstr "" -#: src/gui/view_model/mod.rs:302 src/gui/view_model/mod.rs:367 +#: src/gui/view_model/mod.rs:302 src/gui/view_model/mod.rs:359 msgid "" "No more PIN attempts allowed. Try removing your device and plugging it back " "in." msgstr "" -#: src/gui/view_model/mod.rs:306 src/gui/view_model/mod.rs:371 -msgid "" -"This server requires your device to have additional protection like a PIN, " -"which is not set. Please set a PIN for this device and try again." -msgstr "" - -#: src/gui/view_model/mod.rs:310 src/gui/view_model/mod.rs:375 -msgid "" -"The entered PIN violates the PIN-policy of this device (likely too short). " -"Please try again." -msgstr "" - -#: src/gui/view_model/mod.rs:320 src/gui/view_model/mod.rs:385 +#: src/gui/view_model/mod.rs:308 src/gui/view_model/mod.rs:365 msgid "This credential is already registered on this authenticator." msgstr "" -#: src/gui/view_model/mod.rs:434 +#: src/gui/view_model/mod.rs:413 msgid "Something went wrong. Try again later or use a different authenticator." msgstr "" diff --git a/credentialsd-ui/po/de_DE.po b/credentialsd-ui/po/de_DE.po index 0958adf..c699f30 100644 --- a/credentialsd-ui/po/de_DE.po +++ b/credentialsd-ui/po/de_DE.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \"https://github.com/linux-credentials/credentialsd/" "issues\"\n" -"POT-Creation-Date: 2026-02-12 14:18+0100\n" +"POT-Creation-Date: 2026-03-20 11:40+0100\n" "PO-Revision-Date: 2025-10-10 14:45+0200\n" "Last-Translator: Martin Sirringhaus \n" "Language: de_DE\n" @@ -14,7 +14,7 @@ msgstr "" #: data/xyz.iinuwa.credentialsd.CredentialsUi.desktop.in.in:2 #: data/xyz.iinuwa.credentialsd.CredentialsUi.metainfo.xml.in.in:8 -#: src/gui/view_model/gtk/mod.rs:401 +#: src/gui/view_model/gtk/mod.rs:417 msgid "Credential Manager" msgstr "Zugangsdatenmanager" @@ -128,8 +128,8 @@ msgstr "Fertig!" msgid "Something went wrong." msgstr "Etwas ist schief gegangen." -#: data/resources/ui/window.ui:304 src/gui/view_model/mod.rs:315 -#: src/gui/view_model/mod.rs:380 +#: data/resources/ui/window.ui:304 src/gui/view_model/mod.rs:305 +#: src/gui/view_model/mod.rs:362 msgid "" "Something went wrong while retrieving a credential. Please try again later " "or use a different authenticator." @@ -141,37 +141,63 @@ msgstr "" msgid "Set PIN on device" msgstr "Geräte-PIN festlegen" -#: src/gui/view_model/gtk/mod.rs:154 +#: src/gui/view_model/gtk/mod.rs:156 #, fuzzy msgid "Enter your PIN. One attempt remaining." msgid_plural "Enter your PIN. %d attempts remaining." msgstr[0] "Geben Sie Ihren PIN ein. Sie haben nur noch einen Versuch." msgstr[1] "Geben Sie Ihren PIN ein. Sie haben noch %d Versuche." -#: src/gui/view_model/gtk/mod.rs:160 +#: src/gui/view_model/gtk/mod.rs:162 msgid "Enter your PIN." msgstr "Geben Sie Ihren PIN ein." -#: src/gui/view_model/gtk/mod.rs:170 +#: src/gui/view_model/gtk/mod.rs:172 msgid "Touch your device again. One attempt remaining." msgid_plural "Touch your device again. %d attempts remaining." msgstr[0] "Berühren Sie Ihr Gerät. Sie haben nur noch einen Versuch." msgstr[1] "Berühren Sie nochmal Ihr Gerät. Sie haben nur noch %d Versuche." -#: src/gui/view_model/gtk/mod.rs:176 +#: src/gui/view_model/gtk/mod.rs:178 msgid "Touch your device." msgstr "Berühren Sie Ihr Gerät." -#: src/gui/view_model/gtk/mod.rs:181 +#: src/gui/view_model/gtk/mod.rs:183 msgid "Touch your device" msgstr "Berühren Sie Ihr Gerät." -#: src/gui/view_model/gtk/mod.rs:184 +#: src/gui/view_model/gtk/mod.rs:191 +msgid "" +"This server requires your device to have additional protection like a PIN, " +"which is not set. Please set a PIN for this device and try again." +msgstr "" +"Für diesen Server benötigt ihr Gerät eine zusätzliche Absicherung, z.B. " +"einen PIN. Bitte setzen Sie einen PIN für ihr Gerät und versuchen Sie es " +"erneut." + +#: src/gui/view_model/gtk/mod.rs:195 +msgid "" +"The entered PIN violates the PIN-policy of this device (likely too short). " +"Please try again." +msgstr "" +"Der eingegebene PIN entspricht nicht den PIN-Bestimmungen des Geräts (z.B. " +"zu kurz). Bitte versuchen Sie es erneut." + +#: src/gui/view_model/gtk/mod.rs:198 +#, fuzzy +msgid "" +"The entered PIN violates the PIN-policy of this device (PIN too long). " +"Please try again." +msgstr "" +"Der eingegebene PIN entspricht nicht den PIN-Bestimmungen des Geräts (z.B. " +"zu lang). Bitte versuchen Sie es erneut." + +#: src/gui/view_model/gtk/mod.rs:205 msgid "Scan the QR code with your device to begin authentication." msgstr "" "Scannen Sie den QR code mit ihrem Gerät um die Authentifizierung zu beginnen." -#: src/gui/view_model/gtk/mod.rs:194 +#: src/gui/view_model/gtk/mod.rs:215 msgid "" "Connecting to your device. Make sure both devices are near each other and " "have Bluetooth enabled." @@ -179,15 +205,15 @@ msgstr "" "Verbindung zu Ihrem Gerät wird aufgebaut. Stellen Sie sicher, dass beide " "Geräte nah beieinander sind und Bluetooth aktiviert haben." -#: src/gui/view_model/gtk/mod.rs:202 +#: src/gui/view_model/gtk/mod.rs:223 msgid "Device connected. Follow the instructions on your device" msgstr "Verbindung hergestellt. Folgen Sie den Anweisungen auf Ihrem Gerät." -#: src/gui/view_model/gtk/mod.rs:333 +#: src/gui/view_model/gtk/mod.rs:349 msgid "Insert your security key." msgstr "Stecken Sie Ihren Security-Token ein." -#: src/gui/view_model/gtk/mod.rs:352 +#: src/gui/view_model/gtk/mod.rs:368 msgid "Multiple devices found. Please select with which to proceed." msgstr "Mehrere Geräte gefunden. Bitte wählen Sie einen aus, um fortzufahren." @@ -215,17 +241,17 @@ msgstr "Ein NFC-Gerät" msgid "A security key (USB)" msgstr "Ein Security-Token" -#: src/gui/view_model/mod.rs:75 +#: src/gui/view_model/mod.rs:70 msgid "unknown application" msgstr "unbekannter Applikation" #. TRANSLATORS: %s1 is the "relying party" (think: domain name) where the request is coming from -#: src/gui/view_model/mod.rs:80 +#: src/gui/view_model/mod.rs:86 msgid "Create a passkey for %s1" msgstr "Neuen Passkey für %s1 erstellen" #. TRANSLATORS: %s1 is the "relying party" (think: domain name) where the request is coming from -#: src/gui/view_model/mod.rs:84 +#: src/gui/view_model/mod.rs:90 msgid "Use a passkey for %s1" msgstr "Passkey für %s1 abrufen" @@ -233,7 +259,7 @@ msgstr "Passkey für %s1 abrufen" #. TRANSLATORS: %s2 is the application name (e.g.: firefox) where the request is coming from, must be left untouched to make the name bold #. TRANSLATORS: %i1 is the process ID of the requesting application #. TRANSLATORS: %s3 is the absolute path (think: /usr/bin/firefox) of the requesting application -#: src/gui/view_model/mod.rs:96 +#: src/gui/view_model/mod.rs:102 msgid "" "\"%s2\" (process ID: %i1, binary: %s3) is asking to create a " "credential to register at \"%s1\". Only proceed if you trust this process." @@ -246,7 +272,7 @@ msgstr "" #. TRANSLATORS: %s2 is the application name (e.g.: firefox) where the request is coming from, must be left untouched to make the name bold #. TRANSLATORS: %i1 is the process ID of the requesting application #. TRANSLATORS: %s3 is the absolute path (think: /usr/bin/firefox) of the requesting application -#: src/gui/view_model/mod.rs:103 +#: src/gui/view_model/mod.rs:109 msgid "" "\"%s2\" (process ID: %i1, binary: %s3) is asking to use a credential " "to sign in to \"%s1\". Only proceed if you trust this process." @@ -255,15 +281,15 @@ msgstr "" "abrufen, um Sie bei \"%s1\" anzumelden. Fahren Sie nur fort, wenn Sie diesem " "Prozess vertrauen." -#: src/gui/view_model/mod.rs:244 +#: src/gui/view_model/mod.rs:239 msgid "Failed to select credential from device." msgstr "Zugangsdaten vom Gerät konnten nicht ausgewählt werden." -#: src/gui/view_model/mod.rs:298 src/gui/view_model/mod.rs:363 +#: src/gui/view_model/mod.rs:299 src/gui/view_model/mod.rs:356 msgid "No matching credentials found on this authenticator." msgstr "Keine passenden Zugangsdaten auf diesem Gerät gefunden." -#: src/gui/view_model/mod.rs:302 src/gui/view_model/mod.rs:367 +#: src/gui/view_model/mod.rs:302 src/gui/view_model/mod.rs:359 msgid "" "No more PIN attempts allowed. Try removing your device and plugging it back " "in." @@ -271,28 +297,11 @@ msgstr "" "Keine weiteren PIN-Eingaben erlaubt. Versuchen Sie ihr Gerät aus- und wieder " "einzustecken." -#: src/gui/view_model/mod.rs:306 src/gui/view_model/mod.rs:371 -msgid "" -"This server requires your device to have additional protection like a PIN, " -"which is not set. Please set a PIN for this device and try again." -msgstr "" -"Für diesen Server benötigt ihr Gerät eine zusätzliche Absicherung, z.B. " -"einen PIN. Bitte setzen Sie einen PIN für ihr Gerät und versuchen Sie es " -"erneut." - -#: src/gui/view_model/mod.rs:310 src/gui/view_model/mod.rs:375 -msgid "" -"The entered PIN violates the PIN-policy of this device (likely too short). " -"Please try again." -msgstr "" -"Der eingegebene PIN entspricht nicht den PIN-Bestimmungen des Geräts (z.B. " -"zu kurz). Bitte versuchen Sie es erneut." - -#: src/gui/view_model/mod.rs:320 src/gui/view_model/mod.rs:385 +#: src/gui/view_model/mod.rs:308 src/gui/view_model/mod.rs:365 msgid "This credential is already registered on this authenticator." msgstr "Diese Zugangsdaten sind bereits auf diesem Gerät registriert." -#: src/gui/view_model/mod.rs:434 +#: src/gui/view_model/mod.rs:413 msgid "Something went wrong. Try again later or use a different authenticator." msgstr "" "Es ist ein Fehler aufgetreten. Bitte versuchen Sie es später noch einmal, " diff --git a/credentialsd-ui/po/en_US.po b/credentialsd-ui/po/en_US.po index 89e8ee0..c365447 100644 --- a/credentialsd-ui/po/en_US.po +++ b/credentialsd-ui/po/en_US.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \"https://github.com/linux-credentials/credentialsd/" "issues\"\n" -"POT-Creation-Date: 2026-02-12 14:18+0100\n" +"POT-Creation-Date: 2026-03-20 11:40+0100\n" "PO-Revision-Date: 2025-10-10 14:45+0200\n" "Last-Translator: Martin Sirringhaus \n" "Language: en_US\n" @@ -13,7 +13,7 @@ msgstr "" #: data/xyz.iinuwa.credentialsd.CredentialsUi.desktop.in.in:2 #: data/xyz.iinuwa.credentialsd.CredentialsUi.metainfo.xml.in.in:8 -#: src/gui/view_model/gtk/mod.rs:401 +#: src/gui/view_model/gtk/mod.rs:417 msgid "Credential Manager" msgstr "Credential Manager" @@ -129,8 +129,8 @@ msgstr "Done!" msgid "Something went wrong." msgstr "Something went wrong." -#: data/resources/ui/window.ui:304 src/gui/view_model/mod.rs:315 -#: src/gui/view_model/mod.rs:380 +#: data/resources/ui/window.ui:304 src/gui/view_model/mod.rs:305 +#: src/gui/view_model/mod.rs:362 msgid "" "Something went wrong while retrieving a credential. Please try again later " "or use a different authenticator." @@ -142,35 +142,60 @@ msgstr "" msgid "Set PIN on device" msgstr "Set PIN on device" -#: src/gui/view_model/gtk/mod.rs:154 +#: src/gui/view_model/gtk/mod.rs:156 msgid "Enter your PIN. One attempt remaining." msgid_plural "Enter your PIN. %d attempts remaining." msgstr[0] "Enter your PIN. One attempt remaining." msgstr[1] "Enter your PIN. %d attempts remaining." -#: src/gui/view_model/gtk/mod.rs:160 +#: src/gui/view_model/gtk/mod.rs:162 msgid "Enter your PIN." msgstr "Enter your PIN." -#: src/gui/view_model/gtk/mod.rs:170 +#: src/gui/view_model/gtk/mod.rs:172 msgid "Touch your device again. One attempt remaining." msgid_plural "Touch your device again. %d attempts remaining." msgstr[0] "Touch your device again. One attempt remaining." msgstr[1] "Touch your device again. %d attempts remaining." -#: src/gui/view_model/gtk/mod.rs:176 +#: src/gui/view_model/gtk/mod.rs:178 msgid "Touch your device." msgstr "Touch your device." -#: src/gui/view_model/gtk/mod.rs:181 +#: src/gui/view_model/gtk/mod.rs:183 msgid "Touch your device" msgstr "Touch your device" -#: src/gui/view_model/gtk/mod.rs:184 +#: src/gui/view_model/gtk/mod.rs:191 +msgid "" +"This server requires your device to have additional protection like a PIN, " +"which is not set. Please set a PIN for this device and try again." +msgstr "" +"This server requires your device to have additional protection like a PIN, " +"which is not set. Please set a PIN for this device and try again." + +#: src/gui/view_model/gtk/mod.rs:195 +msgid "" +"The entered PIN violates the PIN-policy of this device (likely too short). " +"Please try again." +msgstr "" +"The entered PIN violates the PIN-policy of this device (likely too short). " +"Please try again." + +#: src/gui/view_model/gtk/mod.rs:198 +#, fuzzy +msgid "" +"The entered PIN violates the PIN-policy of this device (PIN too long). " +"Please try again." +msgstr "" +"The entered PIN violates the PIN-policy of this device (likely too long). " +"Please try again." + +#: src/gui/view_model/gtk/mod.rs:205 msgid "Scan the QR code with your device to begin authentication." msgstr "Scan the QR code with your device to begin authentication." -#: src/gui/view_model/gtk/mod.rs:194 +#: src/gui/view_model/gtk/mod.rs:215 msgid "" "Connecting to your device. Make sure both devices are near each other and " "have Bluetooth enabled." @@ -178,15 +203,15 @@ msgstr "" "Connecting to your device. Make sure both devices are near each other and " "have Bluetooth enabled." -#: src/gui/view_model/gtk/mod.rs:202 +#: src/gui/view_model/gtk/mod.rs:223 msgid "Device connected. Follow the instructions on your device" msgstr "Device connected. Follow the instructions on your device" -#: src/gui/view_model/gtk/mod.rs:333 +#: src/gui/view_model/gtk/mod.rs:349 msgid "Insert your security key." msgstr "Insert your security key." -#: src/gui/view_model/gtk/mod.rs:352 +#: src/gui/view_model/gtk/mod.rs:368 msgid "Multiple devices found. Please select with which to proceed." msgstr "Multiple devices found. Please select with which to proceed." @@ -214,17 +239,17 @@ msgstr "A security key or card (NFC)" msgid "A security key (USB)" msgstr "A security key (USB)" -#: src/gui/view_model/mod.rs:75 +#: src/gui/view_model/mod.rs:70 msgid "unknown application" msgstr "unknown application" #. TRANSLATORS: %s1 is the "relying party" (think: domain name) where the request is coming from -#: src/gui/view_model/mod.rs:80 +#: src/gui/view_model/mod.rs:86 msgid "Create a passkey for %s1" msgstr "Create a passkey for %s1" #. TRANSLATORS: %s1 is the "relying party" (think: domain name) where the request is coming from -#: src/gui/view_model/mod.rs:84 +#: src/gui/view_model/mod.rs:90 msgid "Use a passkey for %s1" msgstr "Use a passkey for %s1" @@ -232,7 +257,7 @@ msgstr "Use a passkey for %s1" #. TRANSLATORS: %s2 is the application name (e.g.: firefox) where the request is coming from, must be left untouched to make the name bold #. TRANSLATORS: %i1 is the process ID of the requesting application #. TRANSLATORS: %s3 is the absolute path (think: /usr/bin/firefox) of the requesting application -#: src/gui/view_model/mod.rs:96 +#: src/gui/view_model/mod.rs:102 msgid "" "\"%s2\" (process ID: %i1, binary: %s3) is asking to create a " "credential to register at \"%s1\". Only proceed if you trust this process." @@ -244,7 +269,7 @@ msgstr "" #. TRANSLATORS: %s2 is the application name (e.g.: firefox) where the request is coming from, must be left untouched to make the name bold #. TRANSLATORS: %i1 is the process ID of the requesting application #. TRANSLATORS: %s3 is the absolute path (think: /usr/bin/firefox) of the requesting application -#: src/gui/view_model/mod.rs:103 +#: src/gui/view_model/mod.rs:109 msgid "" "\"%s2\" (process ID: %i1, binary: %s3) is asking to use a credential " "to sign in to \"%s1\". Only proceed if you trust this process." @@ -252,15 +277,15 @@ msgstr "" "\"%s2\" (process ID: %i1, binary: %s3) is asking to use a credential " "to sign in to \"%s1\". Only proceed if you trust this process." -#: src/gui/view_model/mod.rs:244 +#: src/gui/view_model/mod.rs:239 msgid "Failed to select credential from device." msgstr "Failed to select credential from device." -#: src/gui/view_model/mod.rs:298 src/gui/view_model/mod.rs:363 +#: src/gui/view_model/mod.rs:299 src/gui/view_model/mod.rs:356 msgid "No matching credentials found on this authenticator." msgstr "No matching credentials found on this authenticator." -#: src/gui/view_model/mod.rs:302 src/gui/view_model/mod.rs:367 +#: src/gui/view_model/mod.rs:302 src/gui/view_model/mod.rs:359 msgid "" "No more PIN attempts allowed. Try removing your device and plugging it back " "in." @@ -268,27 +293,11 @@ msgstr "" "No more PIN attempts allowed. Try removing your device and plugging it back " "in." -#: src/gui/view_model/mod.rs:306 src/gui/view_model/mod.rs:371 -msgid "" -"This server requires your device to have additional protection like a PIN, " -"which is not set. Please set a PIN for this device and try again." -msgstr "" -"This server requires your device to have additional protection like a PIN, " -"which is not set. Please set a PIN for this device and try again." - -#: src/gui/view_model/mod.rs:310 src/gui/view_model/mod.rs:375 -msgid "" -"The entered PIN violates the PIN-policy of this device (likely too short). " -"Please try again." -msgstr "" -"The entered PIN violates the PIN-policy of this device (likely too short). " -"Please try again." - -#: src/gui/view_model/mod.rs:320 src/gui/view_model/mod.rs:385 +#: src/gui/view_model/mod.rs:308 src/gui/view_model/mod.rs:365 msgid "This credential is already registered on this authenticator." msgstr "This credential is already registered on this authenticator." -#: src/gui/view_model/mod.rs:434 +#: src/gui/view_model/mod.rs:413 msgid "Something went wrong. Try again later or use a different authenticator." msgstr "" "Something went wrong. Try again later or use a different authenticator." diff --git a/credentialsd-ui/src/client.rs b/credentialsd-ui/src/client.rs index b5cddfa..c7d4c6c 100644 --- a/credentialsd-ui/src/client.rs +++ b/credentialsd-ui/src/client.rs @@ -97,18 +97,10 @@ impl FlowController for DbusCredentialClient { .map_err(|err| tracing::error!("Failed to send PIN to authenticator: {err}")) } - async fn set_usb_device_pin(&mut self, pin: String) -> std::result::Result<(), ()> { + async fn set_device_pin(&mut self, pin: String) -> std::result::Result<(), ()> { self.proxy() .await? - .set_usb_device_pin(pin) - .await - .map_err(|err| tracing::error!("Failed to set new PIN for authenticator: {err}")) - } - - async fn set_nfc_device_pin(&mut self, pin: String) -> std::result::Result<(), ()> { - self.proxy() - .await? - .set_nfc_device_pin(pin) + .set_device_pin(pin) .await .map_err(|err| tracing::error!("Failed to set new PIN for authenticator: {err}")) } diff --git a/credentialsd-ui/src/dbus.rs b/credentialsd-ui/src/dbus.rs index 05a0fad..2293761 100644 --- a/credentialsd-ui/src/dbus.rs +++ b/credentialsd-ui/src/dbus.rs @@ -26,8 +26,7 @@ pub trait FlowControlService { async fn select_credential(&self, credential_id: String) -> fdo::Result<()>; async fn cancel_request(&self, request_id: RequestId) -> fdo::Result<()>; - async fn set_usb_device_pin(&self, pin: String) -> fdo::Result<()>; - async fn set_nfc_device_pin(&self, pin: String) -> fdo::Result<()>; + async fn set_device_pin(&self, pin: String) -> fdo::Result<()>; #[zbus(signal)] async fn state_changed(update: BackgroundEvent) -> zbus::Result<()>; } diff --git a/credentialsd-ui/src/gui/view_model/gtk/mod.rs b/credentialsd-ui/src/gui/view_model/gtk/mod.rs index daa1f4e..4834b26 100644 --- a/credentialsd-ui/src/gui/view_model/gtk/mod.rs +++ b/credentialsd-ui/src/gui/view_model/gtk/mod.rs @@ -4,7 +4,7 @@ pub mod device; mod window; use async_std::channel::{Receiver, Sender}; -use credentialsd_common::model::{ViewUpdateFailure, ViewUpdateSuccess}; +use credentialsd_common::model::PinNotSetError; use credentialsd_common::server::WindowHandle; use gettextrs::{LocaleCategory, gettext, ngettext}; use glib::clone; @@ -132,6 +132,8 @@ impl ViewModel { Ok(update) => { // TODO: hack so I don't have to unset this in every event manually. view_model.set_usb_nfc_pin_entry_visible(false); + view_model.set_start_setting_new_pin_visible(false); + view_model.set_failed(false); match update { ViewUpdate::SetTitle((title, subtitle)) => { view_model.set_title(title); @@ -180,6 +182,25 @@ impl ViewModel { ViewUpdate::UsbNeedsUserPresence => { view_model.set_prompt(gettext("Touch your device")); } + ViewUpdate::UsbPinNotSet { error } + | ViewUpdate::NfcPinNotSet { error } => { + view_model.set_failed(true); + view_model.set_start_setting_new_pin_visible(true); + let text = match error { + None => gettext( + "This server requires your device to have additional protection like a PIN, which is not set. Please set a PIN for this device and try again.", + ), + Some(PinNotSetError::PinTooShort) + | Some(PinNotSetError::PinPolicyViolation) => gettext( + "The entered PIN violates the PIN-policy of this device (likely too short). Please try again.", + ), + Some(PinNotSetError::PinTooLong) => gettext( + "The entered PIN violates the PIN-policy of this device (PIN too long). Please try again.", + ), + }; + // These are already gettext messages + view_model.set_prompt(text); + } ViewUpdate::HybridNeedsQrCode(qr_code) => { view_model.set_prompt(gettext("Scan the QR code with your device to begin authentication.")); let texture = view_model.draw_qr_code(&qr_code); @@ -203,25 +224,15 @@ impl ViewModel { )); view_model.set_qr_spinner_visible(false); } - ViewUpdate::Completed(ViewUpdateSuccess::CloseWindow) => { + ViewUpdate::Completed => { view_model.set_qr_spinner_visible(false); view_model.set_completed(true); } - ViewUpdate::Completed(ViewUpdateSuccess::KeepWindowOpen(text)) => { - view_model.set_qr_spinner_visible(false); - // These are already gettext messages - view_model.set_prompt(text); - } ViewUpdate::Failed(error) => { view_model.set_qr_spinner_visible(false); view_model.set_failed(true); - view_model.set_start_setting_new_pin_visible(matches!( - &error, - ViewUpdateFailure::PinNotSet(_) - | ViewUpdateFailure::PinPolicyViolation(_) - )); // These are already gettext messages - view_model.set_prompt(error.into_string()); + view_model.set_prompt(error); } ViewUpdate::Cancelled => { view_model.set_state(ModelState::Cancelled) diff --git a/credentialsd-ui/src/gui/view_model/gtk/window.rs b/credentialsd-ui/src/gui/view_model/gtk/window.rs index 5946c4c..57feb1a 100644 --- a/credentialsd-ui/src/gui/view_model/gtk/window.rs +++ b/credentialsd-ui/src/gui/view_model/gtk/window.rs @@ -253,6 +253,17 @@ impl CredentialsUiWindow { } )); + view_model.connect_usb_nfc_pin_entry_visible_notify(clone!( + #[weak] + stack, + move |vm| { + // If the entry becomes visible, we definitely need to be on the PIN page + if vm.usb_nfc_pin_entry_visible() { + stack.set_visible_child_name("usb_or_nfc"); + } + } + )); + view_model.connect_pin_fields_match_notify(clone!( #[weak] stack, diff --git a/credentialsd-ui/src/gui/view_model/mod.rs b/credentialsd-ui/src/gui/view_model/mod.rs index 5f68f64..197c5bb 100644 --- a/credentialsd-ui/src/gui/view_model/mod.rs +++ b/credentialsd-ui/src/gui/view_model/mod.rs @@ -7,7 +7,7 @@ use async_std::{ channel::{Receiver, Sender}, sync::Mutex as AsyncMutex, }; -use credentialsd_common::model::{RequestingApplication, ViewUpdateSuccess}; +use credentialsd_common::model::RequestingApplication; use credentialsd_common::server::ViewRequest; use gettextrs::gettext; use serde::{Deserialize, Serialize}; @@ -17,7 +17,7 @@ use credentialsd_common::{ client::FlowController, model::{ BackgroundEvent, Credential, Device, Error, HybridState, NfcState, Operation, Transport, - UsbState, ViewUpdate, ViewUpdateFailure, + UsbState, ViewUpdate, }, }; @@ -89,7 +89,6 @@ impl ViewModel { // TRANSLATORS: %s1 is the "relying party" (think: domain name) where the request is coming from gettext("Use a passkey for %s1") } - Operation::SetDevicePin => gettext("Setting Pin on device"), } .to_string(); title = title.replace("%s1", &self.rp_id); @@ -109,7 +108,6 @@ impl ViewModel { // TRANSLATORS: %s3 is the absolute path (think: /usr/bin/firefox) of the requesting application gettext("\"%s2\" (process ID: %i1, binary: %s3) is asking to use a credential to sign in to \"%s1\". Only proceed if you trust this process.") } - Operation::SetDevicePin => gettext("Setting Pin on device"), } .to_string(); subtitle = subtitle.replace("%s1", &self.rp_id); @@ -216,21 +214,9 @@ impl ViewModel { } } Event::View(ViewEvent::SetNewDevicePin(pin)) => { - if let Some(device) = &self.selected_device { - let mut cred_service = self.flow_controller.lock().await; - self.operation = Operation::SetDevicePin; - let resp = match device.transport { - Transport::Usb => cred_service.set_usb_device_pin(pin).await, - Transport::Nfc => cred_service.set_nfc_device_pin(pin).await, - _ => { - error!("Setting new pin is not supported for this transport!"); - Err(()) - } - }; - - if resp.is_err() { - error!("Failed to send new Pin to device"); - } + let mut cred_service = self.flow_controller.lock().await; + if cred_service.set_device_pin(pin).await.is_err() { + error!("Failed to send new pin to device"); } } Event::View(ViewEvent::CredentialSelected(cred_id)) => { @@ -249,8 +235,8 @@ impl ViewModel { { tracing::error!("Failed to select credential from device."); self.tx_update - .send(ViewUpdate::Failed(ViewUpdateFailure::GeneralFailure( - gettext("Failed to select credential from device."), + .send(ViewUpdate::Failed(gettext( + "Failed to select credential from device.", ))) .await .unwrap(); @@ -272,6 +258,12 @@ impl ViewModel { .await .unwrap(); } + UsbState::PinNotSet { error } => { + self.tx_update + .send(ViewUpdate::UsbPinNotSet { error }) + .await + .unwrap(); + } UsbState::NeedsUserVerification { attempts_left } => { self.tx_update .send(ViewUpdate::UsbNeedsUserVerification { attempts_left }) @@ -285,15 +277,7 @@ impl ViewModel { .unwrap(); } UsbState::Completed => { - let t = if matches!(self.operation, Operation::SetDevicePin) { - ViewUpdateSuccess::KeepWindowOpen(gettext( - "Pin successfully set! Please try registering again.", - )) - } else { - ViewUpdateSuccess::CloseWindow - }; - - self.tx_update.send(ViewUpdate::Completed(t)).await.unwrap(); + self.tx_update.send(ViewUpdate::Completed).await.unwrap(); } UsbState::SelectingDevice => { self.tx_update @@ -311,32 +295,18 @@ impl ViewModel { // TODO: Provide more specific error messages using the wrapped Error. UsbState::Failed(err) => { let error_msg = match err { - Error::NoCredentials => ViewUpdateFailure::GeneralFailure(gettext( - "No matching credentials found on this authenticator.", - )), - Error::PinAttemptsExhausted => { - ViewUpdateFailure::GeneralFailure(gettext( - "No more PIN attempts allowed. Try removing your device and plugging it back in.", - )) - } - Error::PinNotSet => ViewUpdateFailure::PinNotSet(gettext( - "This server requires your device to have additional protection like a PIN, which is not set. Please set a PIN for this device and try again.", - )), - Error::PinPolicyViolation => { - ViewUpdateFailure::PinPolicyViolation(gettext( - "The entered PIN violates the PIN-policy of this device (likely too short). Please try again.", - )) - } - Error::AuthenticatorError | Error::Internal(_) => { - ViewUpdateFailure::GeneralFailure(gettext( - "Something went wrong while retrieving a credential. Please try again later or use a different authenticator.", - )) - } - Error::CredentialExcluded => { - ViewUpdateFailure::GeneralFailure(gettext( - "This credential is already registered on this authenticator.", - )) + Error::NoCredentials => { + gettext("No matching credentials found on this authenticator.") } + Error::PinAttemptsExhausted => gettext( + "No more PIN attempts allowed. Try removing your device and plugging it back in.", + ), + Error::AuthenticatorError | Error::Internal(_) => gettext( + "Something went wrong while retrieving a credential. Please try again later or use a different authenticator.", + ), + Error::CredentialExcluded => gettext( + "This credential is already registered on this authenticator.", + ), }; self.tx_update .send(ViewUpdate::Failed(error_msg)) @@ -363,15 +333,14 @@ impl ViewModel { .await .unwrap(); } + NfcState::PinNotSet { error } => { + self.tx_update + .send(ViewUpdate::NfcPinNotSet { error }) + .await + .unwrap(); + } NfcState::Completed => { - let t = if matches!(self.operation, Operation::SetDevicePin) { - ViewUpdateSuccess::KeepWindowOpen(gettext( - "Pin successfully set! Please try registering again.", - )) - } else { - ViewUpdateSuccess::CloseWindow - }; - self.tx_update.send(ViewUpdate::Completed(t)).await.unwrap(); + self.tx_update.send(ViewUpdate::Completed).await.unwrap(); } NfcState::Idle | NfcState::Waiting => {} NfcState::SelectingCredential { creds } => { @@ -383,32 +352,18 @@ impl ViewModel { // TODO: Provide more specific error messages using the wrapped Error. NfcState::Failed(err) => { let error_msg = match err { - Error::NoCredentials => ViewUpdateFailure::GeneralFailure(gettext( - "No matching credentials found on this authenticator.", - )), - Error::PinAttemptsExhausted => { - ViewUpdateFailure::GeneralFailure(gettext( - "No more PIN attempts allowed. Try removing your device and plugging it back in.", - )) - } - Error::PinNotSet => ViewUpdateFailure::PinNotSet(gettext( - "This server requires your device to have additional protection like a PIN, which is not set. Please set a PIN for this device and try again.", - )), - Error::PinPolicyViolation => { - ViewUpdateFailure::PinPolicyViolation(gettext( - "The entered PIN violates the PIN-policy of this device (likely too short). Please try again.", - )) - } - Error::AuthenticatorError | Error::Internal(_) => { - ViewUpdateFailure::GeneralFailure(gettext( - "Something went wrong while retrieving a credential. Please try again later or use a different authenticator.", - )) - } - Error::CredentialExcluded => { - ViewUpdateFailure::GeneralFailure(gettext( - "This credential is already registered on this authenticator.", - )) + Error::NoCredentials => { + gettext("No matching credentials found on this authenticator.") } + Error::PinAttemptsExhausted => gettext( + "No more PIN attempts allowed. Try removing your device and plugging it back in.", + ), + Error::AuthenticatorError | Error::Internal(_) => gettext( + "Something went wrong while retrieving a credential. Please try again later or use a different authenticator.", + ), + Error::CredentialExcluded => gettext( + "This credential is already registered on this authenticator.", + ), }; self.tx_update .send(ViewUpdate::Failed(error_msg)) @@ -447,10 +402,7 @@ impl ViewModel { } HybridState::Completed => { self.hybrid_qr_code_data = None; - self.tx_update - .send(ViewUpdate::Completed(ViewUpdateSuccess::CloseWindow)) - .await - .unwrap(); + self.tx_update.send(ViewUpdate::Completed).await.unwrap(); } HybridState::UserCancelled => { self.hybrid_qr_code_data = None; @@ -458,7 +410,7 @@ impl ViewModel { } HybridState::Failed => { self.hybrid_qr_code_data = None; - self.tx_update.send(ViewUpdate::Failed(ViewUpdateFailure::GeneralFailure(gettext("Something went wrong. Try again later or use a different authenticator.")))).await.unwrap(); + self.tx_update.send(ViewUpdate::Failed(gettext("Something went wrong. Try again later or use a different authenticator."))).await.unwrap(); } }; } /* diff --git a/credentialsd/Cargo.toml b/credentialsd/Cargo.toml index 07b3d04..3c00343 100644 --- a/credentialsd/Cargo.toml +++ b/credentialsd/Cargo.toml @@ -11,7 +11,7 @@ async-trait = "0.1.89" base64 = "0.22.1" credentialsd-common = { path = "../credentialsd-common" } futures-lite.workspace = true -libwebauthn = { git = "https://github.com/linux-credentials/libwebauthn.git", rev="80545bff16c4e89a930221e90d3141a76303b84b", features = ["libnfc","pcsc"] } +libwebauthn = { git = "https://github.com/msirringhaus/libwebauthn.git", rev="fc41f140f74ea27da1b4c85d3888ae393afcabda", features = ["libnfc","pcsc"] } # TODO: split nfc and pcsc into separate features # Also, 0.6.1 fails to build with non-vendored library. # https://github.com/alexrsagen/rs-nfc1/issues/15 diff --git a/credentialsd/src/credential_service/hybrid.rs b/credentialsd/src/credential_service/hybrid.rs index e86ede9..85cb6d5 100644 --- a/credentialsd/src/credential_service/hybrid.rs +++ b/credentialsd/src/credential_service/hybrid.rs @@ -49,7 +49,6 @@ impl HybridHandler for InternalHybridHandler { CredentialRequest::GetPublicKeyCredentialRequest(_) => { QrCodeOperationHint::GetAssertionRequest } - CredentialRequest::SetDevicePinRequest(_) => unimplemented!(), }; let mut device = CableQrCodeDevice::new_transient(hint); let qr_code = device.qr_code.to_string(); @@ -124,7 +123,6 @@ impl HybridHandler for InternalHybridHandler { } }; } - CredentialRequest::SetDevicePinRequest(_) => unimplemented!(), } }; let terminal_state = match response { diff --git a/credentialsd/src/credential_service/mod.rs b/credentialsd/src/credential_service/mod.rs index c9ee87d..09b593b 100644 --- a/credentialsd/src/credential_service/mod.rs +++ b/credentialsd/src/credential_service/mod.rs @@ -126,12 +126,10 @@ impl< let operation = match &request { CredentialRequest::CreatePublicKeyCredentialRequest(_) => Operation::Create, CredentialRequest::GetPublicKeyCredentialRequest(_) => Operation::Get, - CredentialRequest::SetDevicePinRequest(_) => unimplemented!(), }; let rp_id = match &request { CredentialRequest::CreatePublicKeyCredentialRequest(r) => r.relying_party.id.clone(), CredentialRequest::GetPublicKeyCredentialRequest(r) => r.relying_party_id.clone(), - CredentialRequest::SetDevicePinRequest(_) => unimplemented!(), }; let view_request = ViewRequest { operation, @@ -240,26 +238,6 @@ impl< todo!("Handle error when context is not set up.") } } - - pub fn set_usb_device_pin( - &self, - pin: String, - ) -> Pin + Send + 'static>> { - let request = CredentialRequest::SetDevicePinRequest(pin); - let stream = self.usb_handler.start(&request); - let ctx = self.ctx.clone(); - Box::pin(UsbStateStream { inner: stream, ctx }) - } - - pub fn set_nfc_device_pin( - &self, - pin: String, - ) -> Pin + Send + 'static>> { - let request = CredentialRequest::SetDevicePinRequest(pin); - let stream = self.nfc_handler.start(&request); - let ctx = self.ctx.clone(); - Box::pin(NfcStateStream { inner: stream, ctx }) - } } pub struct HybridStateStream { diff --git a/credentialsd/src/credential_service/nfc.rs b/credentialsd/src/credential_service/nfc.rs index 25fd97a..d6040b6 100644 --- a/credentialsd/src/credential_service/nfc.rs +++ b/credentialsd/src/credential_service/nfc.rs @@ -5,17 +5,17 @@ use base64::{self, engine::general_purpose::URL_SAFE_NO_PAD, Engine}; use futures_lite::Stream; use libwebauthn::{ ops::webauthn::GetAssertionResponse, - pin::PinManagement, + pin::PinNotSetReason, proto::CtapError, transport::{nfc::device::NfcDevice, Channel, Device}, - webauthn::{Error as WebAuthnError, PlatformError, WebAuthn}, + webauthn::{Error as WebAuthnError, WebAuthn}, UvUpdate, }; use tokio::sync::broadcast; use tokio::sync::mpsc::{self, Receiver, Sender, WeakSender}; use tracing::{debug, warn}; -use credentialsd_common::model::{Credential, Error}; +use credentialsd_common::model::{Credential, Error, PinNotSetError}; use crate::model::{CredentialRequest, GetAssertionResponseInternal}; @@ -115,6 +115,9 @@ impl InProcessNfcHandler { attempts_left, pin_tx, }), + Ok(NfcUvMessage::PinNotSet { reason, pin_tx }) => { + Ok(NfcStateInternal::PinNotSet { reason, pin_tx }) + } Ok(NfcUvMessage::NeedsUserVerification { attempts_left }) => { Ok(NfcStateInternal::NeedsUserVerification { attempts_left }) } @@ -142,9 +145,6 @@ impl InProcessNfcHandler { } } }, - Ok(NfcUvMessage::SetPinSuccess) => Ok(NfcStateInternal::Completed( - CredentialResponse::SetDevicePinSuccessRespone, - )), Err(err) => Err(err), }, None => Err(Error::Internal("NFC UV handler channel closed".to_string())), @@ -178,6 +178,7 @@ impl InProcessNfcHandler { Self::process_user_interaction(&mut signal_rx, &cred_tx).await } NfcStateInternal::NeedsPin { .. } + | NfcStateInternal::PinNotSet { .. } | NfcStateInternal::NeedsUserVerification { .. } => { Self::process_user_interaction(&mut signal_rx, &cred_tx).await } @@ -227,10 +228,6 @@ async fn handle_events( NfcUvMessage::ReceivedCredentials(Box::new(response.into())) }) } - CredentialRequest::SetDevicePinRequest(new_pin) => channel - .change_pin(new_pin.to_string(), Duration::from_secs(300)) - .await - .map(|_| NfcUvMessage::SetPinSuccess), CredentialRequest::GetPublicKeyCredentialRequest(get_cred_request) => channel .webauthn_get_assertion(get_cred_request) .await @@ -260,10 +257,6 @@ async fn handle_events( } .map_err(|err| match err { WebAuthnError::Ctap(CtapError::PINAuthBlocked) => Error::PinAttemptsExhausted, - WebAuthnError::Ctap(CtapError::PINNotSet) => Error::PinNotSet, - WebAuthnError::Platform(PlatformError::PinTooShort) - | WebAuthnError::Platform(PlatformError::PinTooLong) - | WebAuthnError::Ctap(CtapError::PINPolicyViolation) => Error::PinPolicyViolation, WebAuthnError::Ctap(CtapError::NoCredentials) => Error::NoCredentials, WebAuthnError::Ctap(CtapError::CredentialExcluded) => Error::CredentialExcluded, _ => Error::AuthenticatorError, @@ -322,6 +315,12 @@ pub(super) enum NfcStateInternal { pin_tx: mpsc::Sender, }, + /// The device needs the PIN to be set. + PinNotSet { + reason: PinNotSetReason, + pin_tx: mpsc::Sender, + }, + /// The device needs on-device user verification. NeedsUserVerification { attempts_left: Option }, @@ -360,6 +359,12 @@ pub enum NfcState { pin_tx: mpsc::Sender, }, + /// The device needs the PIN to be set. + PinNotSet { + reason: PinNotSetReason, + pin_tx: mpsc::Sender, + }, + /// The device needs on-device user verification. NeedsUserVerification { attempts_left: Option }, // TODO: implement cancellation @@ -393,6 +398,9 @@ impl From for NfcState { attempts_left, pin_tx, }, + NfcStateInternal::PinNotSet { reason, pin_tx } => { + NfcState::PinNotSet { reason, pin_tx } + } NfcStateInternal::NeedsUserVerification { attempts_left } => { NfcState::NeedsUserVerification { attempts_left } } @@ -451,6 +459,15 @@ impl From<&NfcState> for credentialsd_common::model::NfcState { attempts_left: *attempts_left, } } + NfcState::PinNotSet { reason, .. } => { + let error = match reason { + PinNotSetReason::PinNotSet => None, + PinNotSetReason::PinTooShort => Some(PinNotSetError::PinTooShort), + PinNotSetReason::PinTooLong => Some(PinNotSetError::PinTooLong), + PinNotSetReason::PinPolicyViolation => Some(PinNotSetError::PinPolicyViolation), + }; + credentialsd_common::model::NfcState::PinNotSet { error } + } NfcState::NeedsUserVerification { attempts_left } => { credentialsd_common::model::NfcState::NeedsUserVerification { attempts_left: *attempts_left, @@ -504,6 +521,25 @@ async fn handle_nfc_updates( None => tracing::debug!("Pin channel closed before receiving pin from client."), } } + UvUpdate::PinNotSet(pin_update) => { + let (pin_tx, mut pin_rx) = mpsc::channel(1); + if let Err(err) = signal_tx + .send(Ok(NfcUvMessage::PinNotSet { + pin_tx, + reason: pin_update.reason, + })) + .await + { + tracing::error!("Authenticator requested a PIN from the user, but we cannot relay the message to the credential service: {:?}", err); + } + match pin_rx.recv().await { + Some(pin) => match pin_update.set_pin(&pin) { + Ok(()) => {} + Err(err) => tracing::error!("Error sending pin to device: {:?}", err), + }, + None => tracing::debug!("Pin channel closed before receiving pin from client."), + } + } UvUpdate::PresenceRequired => { tracing::debug!("Authenticator requested user presence, but that makes no sense for NFC. Skipping"); } @@ -518,9 +554,12 @@ enum NfcUvMessage { attempts_left: Option, pin_tx: mpsc::Sender, }, + PinNotSet { + reason: PinNotSetReason, + pin_tx: mpsc::Sender, + }, NeedsUserVerification { attempts_left: Option, }, ReceivedCredentials(Box), - SetPinSuccess, } diff --git a/credentialsd/src/credential_service/usb.rs b/credentialsd/src/credential_service/usb.rs index 892d1ca..2fe87b8 100644 --- a/credentialsd/src/credential_service/usb.rs +++ b/credentialsd/src/credential_service/usb.rs @@ -5,20 +5,20 @@ use base64::{self, engine::general_purpose::URL_SAFE_NO_PAD, Engine}; use futures_lite::Stream; use libwebauthn::{ ops::webauthn::GetAssertionResponse, - pin::PinManagement, + pin::PinNotSetReason, proto::CtapError, transport::{ hid::{channel::HidChannelHandle, HidDevice}, Channel, Device, }, - webauthn::{Error as WebAuthnError, PlatformError, WebAuthn}, + webauthn::{Error as WebAuthnError, WebAuthn}, UvUpdate, }; use tokio::sync::broadcast; use tokio::sync::mpsc::{self, Receiver, Sender, WeakSender}; use tracing::{debug, warn}; -use credentialsd_common::model::{Credential, Error}; +use credentialsd_common::model::{Credential, Error, PinNotSetError}; use crate::model::{CredentialRequest, GetAssertionResponseInternal}; @@ -200,6 +200,9 @@ impl InProcessUsbHandler { attempts_left, pin_tx, }), + Ok(UsbUvMessage::PinNotSet { reason, pin_tx }) => { + Ok(UsbStateInternal::PinNotSet { reason, pin_tx }) + } Ok(UsbUvMessage::NeedsUserVerification { attempts_left }) => { Ok(UsbStateInternal::NeedsUserVerification { attempts_left }) } @@ -228,9 +231,6 @@ impl InProcessUsbHandler { } } }, - Ok(UsbUvMessage::SetPinSuccess) => Ok(UsbStateInternal::Completed( - CredentialResponse::SetDevicePinSuccessRespone, - )), Err(err) => Err(err), }, None => Err(Error::Internal("USB UV handler channel closed".to_string())), @@ -267,6 +267,7 @@ impl InProcessUsbHandler { Self::process_user_interaction(&mut signal_rx, &cred_tx).await } UsbStateInternal::NeedsPin { .. } + | UsbStateInternal::PinNotSet { .. } | UsbStateInternal::NeedsUserVerification { .. } | UsbStateInternal::NeedsUserPresence => { Self::process_user_interaction(&mut signal_rx, &cred_tx).await @@ -323,10 +324,6 @@ async fn handle_events( .map(|response| { UsbUvMessage::ReceivedCredentials(Box::new(response.into())) }), - CredentialRequest::SetDevicePinRequest(new_pin) => channel - .change_pin(new_pin.to_string(), Duration::from_secs(300)) - .await - .map(|_| UsbUvMessage::SetPinSuccess), }; match response { Ok(response) => { @@ -350,10 +347,6 @@ async fn handle_events( } .map_err(|err| match err { WebAuthnError::Ctap(CtapError::PINAuthBlocked) => Error::PinAttemptsExhausted, - WebAuthnError::Ctap(CtapError::PINNotSet) => Error::PinNotSet, - WebAuthnError::Platform(PlatformError::PinTooShort) - | WebAuthnError::Platform(PlatformError::PinTooLong) - | WebAuthnError::Ctap(CtapError::PINPolicyViolation) => Error::PinPolicyViolation, WebAuthnError::Ctap(CtapError::NoCredentials) => Error::NoCredentials, WebAuthnError::Ctap(CtapError::CredentialExcluded) => Error::CredentialExcluded, _ => Error::AuthenticatorError, @@ -416,6 +409,12 @@ pub(super) enum UsbStateInternal { pin_tx: mpsc::Sender, }, + /// The device needs the PIN to be entered. + PinNotSet { + reason: PinNotSetReason, + pin_tx: mpsc::Sender, + }, + /// The device needs on-device user verification. NeedsUserVerification { attempts_left: Option }, @@ -461,6 +460,12 @@ pub enum UsbState { pin_tx: mpsc::Sender, }, + /// The device needs the PIN to be set. + PinNotSet { + reason: PinNotSetReason, + pin_tx: mpsc::Sender, + }, + /// The device needs on-device user verification. NeedsUserVerification { attempts_left: Option, @@ -499,6 +504,9 @@ impl From for UsbState { attempts_left, pin_tx, }, + UsbStateInternal::PinNotSet { reason, pin_tx } => { + UsbState::PinNotSet { reason, pin_tx } + } UsbStateInternal::NeedsUserVerification { attempts_left } => { UsbState::NeedsUserVerification { attempts_left } } @@ -560,6 +568,15 @@ impl From<&UsbState> for credentialsd_common::model::UsbState { attempts_left: *attempts_left, } } + UsbState::PinNotSet { reason, .. } => { + let error = match reason { + PinNotSetReason::PinNotSet => None, + PinNotSetReason::PinTooShort => Some(PinNotSetError::PinTooShort), + PinNotSetReason::PinTooLong => Some(PinNotSetError::PinTooLong), + PinNotSetReason::PinPolicyViolation => Some(PinNotSetError::PinPolicyViolation), + }; + credentialsd_common::model::UsbState::PinNotSet { error } + } UsbState::NeedsUserVerification { attempts_left } => { credentialsd_common::model::UsbState::NeedsUserVerification { attempts_left: *attempts_left, @@ -614,6 +631,25 @@ async fn handle_usb_updates( None => tracing::debug!("Pin channel closed before receiving pin from client."), } } + UvUpdate::PinNotSet(pin_update) => { + let (pin_tx, mut pin_rx) = mpsc::channel(1); + if let Err(err) = signal_tx + .send(Ok(UsbUvMessage::PinNotSet { + reason: pin_update.reason, + pin_tx, + })) + .await + { + tracing::error!("Authenticator requested a PIN from the user, but we cannot relay the message to the credential service: {:?}", err); + } + match pin_rx.recv().await { + Some(pin) => match pin_update.set_pin(&pin) { + Ok(()) => {} + Err(err) => tracing::error!("Error sending pin to device: {:?}", err), + }, + None => tracing::debug!("Pin channel closed before receiving pin from client."), + } + } UvUpdate::PresenceRequired => { if let Err(err) = signal_tx.send(Ok(UsbUvMessage::NeedsUserPresence)).await { tracing::error!("Authenticator requested user presence, but we cannot relay the message to the credential service: {:?}", err); @@ -630,10 +666,13 @@ enum UsbUvMessage { attempts_left: Option, pin_tx: mpsc::Sender, }, + PinNotSet { + reason: PinNotSetReason, + pin_tx: mpsc::Sender, + }, NeedsUserVerification { attempts_left: Option, }, NeedsUserPresence, ReceivedCredentials(Box), - SetPinSuccess, } diff --git a/credentialsd/src/dbus/flow_control.rs b/credentialsd/src/dbus/flow_control.rs index 9f7b8ed..0ec58fa 100644 --- a/credentialsd/src/dbus/flow_control.rs +++ b/credentialsd/src/dbus/flow_control.rs @@ -63,6 +63,7 @@ pub async fn start_flow_control_service< signal_state: Arc::new(AsyncMutex::new(SignalState::Idle)), svc, pin_tx: Arc::new(AsyncMutex::new(None)), + set_pin_tx: Arc::new(AsyncMutex::new(None)), cred_tx: Arc::new(AsyncMutex::new(None)), usb_event_forwarder_task: Arc::new(AsyncMutex::new(None)), nfc_event_forwarder_task: Arc::new(AsyncMutex::new(None)), @@ -88,6 +89,7 @@ struct FlowControlService>, svc: Arc>>, pin_tx: Arc>>>, + set_pin_tx: Arc>>>, cred_tx: Arc>>>, usb_event_forwarder_task: Arc>>, nfc_event_forwarder_task: Arc>>, @@ -191,6 +193,7 @@ where ) -> fdo::Result<()> { let mut stream = self.svc.lock().await.get_usb_credential(); let usb_pin_tx = self.pin_tx.clone(); + let usb_set_pin_tx = self.set_pin_tx.clone(); let usb_cred_tx = self.cred_tx.clone(); let signal_state = self.signal_state.clone(); let object_server = object_server.clone(); @@ -217,6 +220,10 @@ where let mut usb_pin_tx = usb_pin_tx.lock().await; let _ = usb_pin_tx.insert(pin_tx); } + UsbState::PinNotSet { pin_tx, .. } => { + let mut usb_set_pin_tx = usb_set_pin_tx.lock().await; + let _ = usb_set_pin_tx.insert(pin_tx); + } UsbState::SelectingCredential { cred_tx, .. } => { let mut usb_cred_tx = usb_cred_tx.lock().await; let _ = usb_cred_tx.insert(cred_tx); @@ -292,112 +299,13 @@ where Ok(()) } - async fn set_usb_device_pin( + async fn set_device_pin( &self, pin: String, #[zbus(object_server)] object_server: &ObjectServer, ) -> fdo::Result<()> { - let mut stream = self.svc.lock().await.set_usb_device_pin(pin); - let usb_cred_tx = self.cred_tx.clone(); - let signal_state = self.signal_state.clone(); - let object_server = object_server.clone(); - let task = tokio::spawn(async move { - let interface: zbus::Result>> = - object_server.interface(SERVICE_PATH).await; - - let emitter = match interface { - Ok(ref i) => i.signal_emitter(), - Err(err) => { - tracing::error!("Failed to get connection to D-Bus to send signals: {err}"); - return; - } - }; - while let Some(state) = stream.next().await { - let event = - credentialsd_common::model::BackgroundEvent::UsbStateChanged((&state).into()); - if let Err(err) = send_state_update(emitter, &signal_state, event).await { - tracing::error!("Failed to send state update to UI: {err}"); - break; - }; - match state { - UsbState::NeedsPin { .. } => { - tracing::error!( - "We are setting a PIN, but the device asks us for one. Aborting" - ); - break; - } - UsbState::SelectingCredential { cred_tx, .. } => { - // TODO: This is not great. The user potentially already selected a device, - // but we are starting a new request cycle, so they have to select one - // again... But the previous cycle has already ended. - let mut usb_cred_tx = usb_cred_tx.lock().await; - let _ = usb_cred_tx.insert(cred_tx); - } - UsbState::Completed | UsbState::Failed(_) => { - break; - } - _ => {} - }; - } - }) - .abort_handle(); - if let Some(prev_task) = self.usb_event_forwarder_task.lock().await.replace(task) { - prev_task.abort(); - } - Ok(()) - } - - async fn set_nfc_device_pin( - &self, - pin: String, - #[zbus(object_server)] object_server: &ObjectServer, - ) -> fdo::Result<()> { - let mut stream = self.svc.lock().await.set_nfc_device_pin(pin); - let usb_cred_tx = self.cred_tx.clone(); - let signal_state = self.signal_state.clone(); - let object_server = object_server.clone(); - let task = tokio::spawn(async move { - let interface: zbus::Result>> = - object_server.interface(SERVICE_PATH).await; - - let emitter = match interface { - Ok(ref i) => i.signal_emitter(), - Err(err) => { - tracing::error!("Failed to get connection to D-Bus to send signals: {err}"); - return; - } - }; - while let Some(state) = stream.next().await { - let event = - credentialsd_common::model::BackgroundEvent::NfcStateChanged((&state).into()); - if let Err(err) = send_state_update(emitter, &signal_state, event).await { - tracing::error!("Failed to send state update to UI: {err}"); - break; - }; - match state { - NfcState::NeedsPin { .. } => { - tracing::error!( - "We are setting a PIN, but the device asks us for one. Aborting" - ); - break; - } - NfcState::SelectCredential { cred_tx, .. } => { - // TODO: This is not great. The user already selected a device, - // but we are starting a new request cycle, so they have - // to select one again... - let mut usb_cred_tx = usb_cred_tx.lock().await; - let _ = usb_cred_tx.insert(cred_tx); - } - NfcState::Completed | NfcState::Failed(_) => { - break; - } - _ => {} - }; - } - }) - .abort_handle(); - if let Some(prev_task) = self.nfc_event_forwarder_task.lock().await.replace(task) { - prev_task.abort(); + if let Some(set_pin_tx) = self.set_pin_tx.lock().await.take() { + set_pin_tx.send(pin).await.unwrap(); } Ok(()) } @@ -529,8 +437,7 @@ pub mod test { #[allow(clippy::enum_variant_names)] #[derive(Debug)] pub enum DummyFlowRequest { - SetUsbDevicePin(String), - SetNfcDevicePin(String), + SetDevicePin(String), EnterClientPin(String), GetDevices, GetHybridCredential, @@ -650,19 +557,9 @@ pub mod test { } } - async fn set_usb_device_pin(&mut self, pin: String) -> Result<(), ()> { + async fn set_device_pin(&mut self, pin: String) -> Result<(), ()> { if let Ok(DummyFlowResponse::SetNewDevicePin(Ok(()))) = - self.send(DummyFlowRequest::SetUsbDevicePin(pin)).await - { - Ok(()) - } else { - Err(()) - } - } - - async fn set_nfc_device_pin(&mut self, pin: String) -> Result<(), ()> { - if let Ok(DummyFlowResponse::SetNewDevicePin(Ok(()))) = - self.send(DummyFlowRequest::SetNfcDevicePin(pin)).await + self.send(DummyFlowRequest::SetDevicePin(pin)).await { Ok(()) } else { @@ -691,6 +588,7 @@ pub mod test { svc: Arc>>, bg_event_tx: Option>, pin_tx: Arc>>>, + set_pin_tx: Arc>>>, usb_event_forwarder_task: Arc>>, nfc_event_forwarder_task: Arc>>, hybrid_event_forwarder_task: Arc>>, @@ -732,6 +630,7 @@ pub mod test { svc, bg_event_tx: None, pin_tx: Arc::new(AsyncMutex::new(None)), + set_pin_tx: Arc::new(AsyncMutex::new(None)), usb_event_forwarder_task: Arc::new(Mutex::new(None)), nfc_event_forwarder_task: Arc::new(Mutex::new(None)), hybrid_event_forwarder_task: Arc::new(Mutex::new(None)), @@ -748,12 +647,8 @@ pub mod test { let rsp = self.enter_client_pin(pin).await; DummyFlowResponse::EnterClientPin(rsp) } - DummyFlowRequest::SetUsbDevicePin(pin) => { - let rsp = self.set_usb_device_pin(pin).await; - DummyFlowResponse::SetNewDevicePin(rsp) - } - DummyFlowRequest::SetNfcDevicePin(pin) => { - let rsp = self.set_nfc_device_pin(pin).await; + DummyFlowRequest::SetDevicePin(pin) => { + let rsp = self.set_device_pin(pin).await; DummyFlowResponse::SetNewDevicePin(rsp) } DummyFlowRequest::GetDevices => { @@ -841,6 +736,7 @@ pub mod test { let mut stream = self.svc.lock().await.get_usb_credential(); if let Some(tx_weak) = self.bg_event_tx.as_ref().map(|t| t.clone().downgrade()) { let usb_pin_tx = self.pin_tx.clone(); + let usb_set_pin_tx = self.set_pin_tx.clone(); let task = tokio::spawn(async move { while let Some(state) = stream.next().await { if let Some(tx) = tx_weak.upgrade() { @@ -857,6 +753,10 @@ pub mod test { let mut usb_pin_tx = usb_pin_tx.lock().await; let _ = usb_pin_tx.insert(pin_tx); } + UsbState::PinNotSet { pin_tx, .. } => { + let mut usb_set_pin_tx = usb_set_pin_tx.lock().await; + let _ = usb_set_pin_tx.insert(pin_tx); + } UsbState::Completed | UsbState::Failed(_) => { break; } @@ -937,12 +837,11 @@ pub mod test { Ok(()) } - async fn set_usb_device_pin(&self, _pin: String) -> Result<(), ()> { - todo!(); - } - - async fn set_nfc_device_pin(&self, _pin: String) -> Result<(), ()> { - todo!(); + async fn set_device_pin(&self, pin: String) -> Result<(), ()> { + if let Some(set_pin_tx) = self.set_pin_tx.lock().await.take() { + set_pin_tx.send(pin).await.unwrap(); + } + Ok(()) } async fn select_credential(&self, _credential_id: String) -> Result<(), ()> { diff --git a/credentialsd/src/model.rs b/credentialsd/src/model.rs index ee25750..cb59df9 100644 --- a/credentialsd/src/model.rs +++ b/credentialsd/src/model.rs @@ -6,14 +6,12 @@ use libwebauthn::ops::webauthn::{ pub enum CredentialRequest { CreatePublicKeyCredentialRequest(MakeCredentialRequest), GetPublicKeyCredentialRequest(GetAssertionRequest), - SetDevicePinRequest(String), } #[derive(Clone, Debug)] pub enum CredentialResponse { CreatePublicKeyCredentialResponse(Box), GetPublicKeyCredentialResponse(Box), - SetDevicePinSuccessRespone, } impl CredentialResponse {