src/eric7/WebBrowser/WebAuth/Fido2Management.py

branch
eric7
changeset 10857
abcb288e7e17
parent 10856
b19cefceca15
child 10858
8a03d5f6146c
diff -r b19cefceca15 -r abcb288e7e17 src/eric7/WebBrowser/WebAuth/Fido2Management.py
--- a/src/eric7/WebBrowser/WebAuth/Fido2Management.py	Mon Jul 22 10:15:41 2024 +0200
+++ b/src/eric7/WebBrowser/WebAuth/Fido2Management.py	Mon Jul 22 15:24:27 2024 +0200
@@ -9,7 +9,7 @@
 import time
 
 from fido2.ctap import CtapError
-from fido2.ctap2 import ClientPin, CredentialManagement, Ctap2
+from fido2.ctap2 import ClientPin, Config, CredentialManagement, Ctap2
 from fido2.hid import CtapHidDevice
 from fido2.webauthn import PublicKeyCredentialUserEntity
 from PyQt6.QtCore import QCoreApplication, QObject, QThread, pyqtSignal
@@ -168,10 +168,13 @@
 
         # PIN related data
         if self.__ctap2.info.options["clientPin"]:
-            if self.__ctap2.info.force_pin_change:
-                msg = self.tr(
+            msg1 = (
+                self.tr(
                     "PIN is disabled and must be changed before it can be used!"
                 )
+                if self.__ctap2.info.force_pin_change
+                else ""
+            )
             pinRetries, powerCycle = self.getPinRetries()
             if pinRetries:
                 if powerCycle:
@@ -183,10 +186,16 @@
                     msg = self.tr("%n attempts remaining", "", pinRetries)
             else:
                 msg = self.tr("PIN is blocked. The security key needs to be reset.")
+            if msg1:
+                msg += "\n" + msg1
         else:
             msg = self.tr("A PIN has not been set.")
         data["pin"].append((self.tr("PIN"), msg))
 
+        data["pin"].append(
+            (self.tr("Minimum PIN length"), str(self.__ctap2.info.min_pin_length))
+        )
+
         alwaysUv = self.__ctap2.info.options.get("alwaysUv")
         msg = (
             self.tr("not supported")
@@ -554,7 +563,7 @@
 
         return self.__ctap2.info.options.get("clientPin")
 
-    def forcedPinChange(self):
+    def pinChangeRequired(self):
         """
         Public method to check for a forced PIN change.
 
@@ -602,6 +611,7 @@
 
         try:
             self.__clientPin.change_pin(old_pin=oldPin, new_pin=newPin)
+            self.reconnectToDevice()
             return True, self.tr("PIN was changed successfully.")
         except CtapError as err:
             return (
@@ -625,6 +635,7 @@
 
         try:
             self.__clientPin.set_pin(pin=pin)
+            self.reconnectToDevice()
             return True, self.tr("PIN was set successfully.")
         except CtapError as err:
             return (
@@ -804,3 +815,162 @@
             )
 
         return CredentialManagement(self.__ctap2, self.__clientPin.protocol, pinToken)
+
+    ############################################################################
+    ## methods related to configuration handling
+    ############################################################################
+
+    def __initConfig(self, pin):
+        """
+        Private method to initialize a configuration object.
+
+        @param pin PIN to unlock the connected security key
+        @type str
+        @return reference to the configuration object
+        @rtype Config
+        @exception Fido2DeviceError raised to indicate an issue with the selected
+            security key
+        @exception Fido2PinError raised to indicate an issue with the PIN
+        """
+        if self.__clientPin is None:
+            self.__clientPin = ClientPin(self.__ctap2)
+
+        if pin == "":
+            pin = self.__pin
+        if pin is None:
+            # Error
+            raise Fido2PinError(
+                self.tr(
+                    "The selected security key is not unlocked or no PIN was entered."
+                )
+            )
+
+        if not Config.is_supported(self.__ctap2.info):
+            raise Fido2DeviceError(
+                self.tr("The selected security key does not support configuration.")
+            )
+
+        try:
+            pinToken = self.__clientPin.get_pin_token(
+                pin, ClientPin.PERMISSION.AUTHENTICATOR_CFG
+            )
+        except CtapError as err:
+            raise Fido2PinError(
+                self.tr("PIN error: {0}").format(self.__pinErrorMessage(err))
+            )
+        except OSError:
+            raise Fido2DeviceError(
+                self.tr("Connected security key unplugged. Reinsert and try again.")
+            )
+
+        return Config(self.__ctap2, self.__clientPin.protocol, pinToken)
+
+    def forcePinChangeSupported(self):
+        """
+        Public method to check, if the 'forcePinChange' function is supported by the
+        selected security key.
+        
+        @return flag indicating support
+        @rtype bool
+        """
+        if (
+            self.__ctap2 is None
+            or self.__ctap2.info is None
+            or not self.__ctap2.info.options.get("setMinPINLength")
+        ):
+            return False
+        else:
+            return True
+
+    def forcePinChange(self, pin):
+        """
+        Public method to force the PIN to be changed to a new value before use.
+
+        @param pin PIN to unlock the connected security key
+        @type str
+        """
+        config = self.__initConfig(pin)
+        config.set_min_pin_length(force_change_pin=True)
+        self.reconnectToDevice()
+
+    def canSetMinimumPinLength(self):
+        """
+        Public method to check, if the 'setMinPINLength' function is available.
+
+        @return flag indicating availability
+        @rtype bool
+        """
+        if (
+            self.__ctap2 is None
+            or self.__ctap2.info is None
+            or not self.__ctap2.info.options.get("setMinPINLength")
+            or (
+                self.__ctap2.info.options.get("alwaysUv")
+                and not self.__ctap2.info.options.get("clientPin")
+            )
+        ):
+            return False
+        else:
+            return True
+
+    def setMinimumPinLength(self, pin, minLength):
+        """
+        Public method to set the minimum PIN length.
+
+        @param pin PIN to unlock the connected security key
+        @type str
+        @param minLength minimum PIN length
+        @type int
+        """
+        if minLength < 4 or minLength > 63:
+            raise Fido2PinError(
+                self.tr("The minimum PIN length must be between 4 and 63.")
+            )
+        if minLength < self.__ctap2.info.min_pin_length:
+            raise Fido2PinError(
+                self.tr("The minimum PIN length must be at least {0}.").format(
+                    self.__ctap2.info.min_pin_length
+                )
+            )
+
+        config = self.__initConfig(pin)
+        config.set_min_pin_length(min_pin_length=minLength)
+        self.reconnectToDevice()
+
+    def canToggleAlwaysUv(self):
+        """
+        Public method to check, if the 'toggleAlwaysUv' function is available.
+
+        @return flag indicating availability
+        @rtype bool
+        """
+        if (
+            self.__ctap2 is None
+            or self.__ctap2.info is None
+            or "alwaysUv" not in self.__ctap2.info.options
+        ):
+            return False
+        else:
+            return True
+
+    def getAlwaysUv(self):
+        """
+        Public method to get the value of the 'alwaysUv' flag of the current security
+        key.
+        """
+        if self.__ctap2 is None:
+            return False
+
+        info = self.__ctap2.get_info()
+        return info is not None and info.options.get("alwaysUv", False)
+
+    def toggleAlwaysUv(self, pin):
+        """
+        Public method to toggle the 'alwaysUv' flag of the selected security key.
+
+        @param pin PIN to unlock the connected security key
+        @type str
+        """
+        config = self.__initConfig(pin)
+        config.toggle_always_uv()
+        self.reconnectToDevice()

eric ide

mercurial