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 a2352dd..dac03f1 100644 --- a/credentialsd-common/src/model.rs +++ b/credentialsd-common/src/model.rs @@ -109,6 +109,25 @@ 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)] pub enum ViewUpdate { SetTitle((String, String)), @@ -131,7 +150,7 @@ pub enum ViewUpdate { Completed, Cancelled, - Failed(String), + Failed(ViewUpdateFailure), } #[derive(Clone, Debug, Default)] @@ -262,6 +281,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), @@ -274,6 +295,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 1c553f7..e997b93 100644 --- a/credentialsd-common/src/server.rs +++ b/credentialsd-common/src/server.rs @@ -220,6 +220,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, @@ -438,6 +439,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, @@ -561,6 +563,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/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..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-03 10:40+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:385 +#: src/gui/view_model/gtk/mod.rs:401 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: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." 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:333 msgid "Insert your security key." msgstr "" -#: src/gui/view_model/gtk/mod.rs:340 +#: src/gui/view_model/gtk/mod.rs:352 msgid "Multiple devices found. Please select with which to proceed." msgstr "" @@ -226,30 +255,36 @@ 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:363 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:367 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: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:293 +#: 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:395 +#: 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 1a846e1..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-03 10:40+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:385 +#: src/gui/view_model/gtk/mod.rs:401 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: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." @@ -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:333 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:352 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:363 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:367 msgid "" "No more PIN attempts allowed. Try removing your device and plugging it back " "in." @@ -242,19 +271,28 @@ 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: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." +"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:293 +#: 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:395 +#: 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 bdbfce9..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-03 10:40+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:385 +#: src/gui/view_model/gtk/mod.rs:401 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: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." @@ -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:333 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:352 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:363 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:367 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: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." @@ -247,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:293 +#: 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:395 +#: 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/client.rs b/credentialsd-ui/src/client.rs index adf716f..723b90d 100644 --- a/credentialsd-ui/src/client.rs +++ b/credentialsd-ui/src/client.rs @@ -99,6 +99,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 2bce739..9ce72a6 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..ac6480a 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,16 @@ 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(_) + | ViewUpdateFailure::PinPolicyViolation(_) + )); // 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 +357,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 bb2d812..ac6d038 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, }, }; @@ -207,6 +207,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: {:?}", @@ -223,8 +240,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(); @@ -277,21 +294,32 @@ 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::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.", + )) + } }; self.tx_update .send(ViewUpdate::Failed(error_msg)) @@ -330,23 +358,34 @@ 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." + ViewUpdateFailure::GeneralFailure(gettext( + "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." + 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(_) => { - "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 @@ -392,7 +431,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(); } }; } /* @@ -411,6 +450,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 7cc373f..5da7c7a 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, @@ -239,6 +241,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 aeecd2d..0807fbb 100644 --- a/credentialsd/src/credential_service/nfc.rs +++ b/credentialsd/src/credential_service/nfc.rs @@ -5,9 +5,10 @@ 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}, + webauthn::{Error as WebAuthnError, PlatformError, WebAuthn}, UvUpdate, }; use tokio::sync::broadcast; @@ -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 @@ -253,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, @@ -511,4 +522,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 46b199f..db14793 100644 --- a/credentialsd/src/credential_service/usb.rs +++ b/credentialsd/src/credential_service/usb.rs @@ -5,12 +5,13 @@ 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}, Channel, Device, }, - webauthn::{Error as WebAuthnError, WebAuthn}, + webauthn::{Error as WebAuthnError, PlatformError, WebAuthn}, UvUpdate, }; use tokio::sync::broadcast; @@ -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) => { @@ -343,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, @@ -624,4 +635,5 @@ enum UsbUvMessage { }, NeedsUserPresence, ReceivedCredentials(Box), + SetPinSuccess, } diff --git a/credentialsd/src/dbus/flow_control.rs b/credentialsd/src/dbus/flow_control.rs index 3ba5068..ff73a40 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(); @@ -415,6 +525,8 @@ pub mod test { #[allow(clippy::enum_variant_names)] #[derive(Debug)] pub enum DummyFlowRequest { + SetUsbDevicePin(String), + SetNfcDevicePin(String), EnterClientPin(String), GetDevices, GetHybridCredential, @@ -427,6 +539,7 @@ pub mod test { // intentional for now. #[allow(clippy::enum_variant_names)] pub enum DummyFlowResponse { + SetNewDevicePin(Result<(), ()>), EnterClientPin(Result<(), ()>), GetDevices(Vec), GetHybridCredential, @@ -438,6 +551,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(), @@ -530,6 +646,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!(); } @@ -608,6 +744,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) @@ -789,6 +933,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 {