src/eric7/WebBrowser/WebAuth/Fido2Management.py

branch
eric7
changeset 10857
abcb288e7e17
parent 10856
b19cefceca15
child 10858
8a03d5f6146c
equal deleted inserted replaced
10856:b19cefceca15 10857:abcb288e7e17
7 """ 7 """
8 8
9 import time 9 import time
10 10
11 from fido2.ctap import CtapError 11 from fido2.ctap import CtapError
12 from fido2.ctap2 import ClientPin, CredentialManagement, Ctap2 12 from fido2.ctap2 import ClientPin, Config, CredentialManagement, Ctap2
13 from fido2.hid import CtapHidDevice 13 from fido2.hid import CtapHidDevice
14 from fido2.webauthn import PublicKeyCredentialUserEntity 14 from fido2.webauthn import PublicKeyCredentialUserEntity
15 from PyQt6.QtCore import QCoreApplication, QObject, QThread, pyqtSignal 15 from PyQt6.QtCore import QCoreApplication, QObject, QThread, pyqtSignal
16 16
17 17
166 "extensions": [], 166 "extensions": [],
167 } 167 }
168 168
169 # PIN related data 169 # PIN related data
170 if self.__ctap2.info.options["clientPin"]: 170 if self.__ctap2.info.options["clientPin"]:
171 if self.__ctap2.info.force_pin_change: 171 msg1 = (
172 msg = self.tr( 172 self.tr(
173 "PIN is disabled and must be changed before it can be used!" 173 "PIN is disabled and must be changed before it can be used!"
174 ) 174 )
175 if self.__ctap2.info.force_pin_change
176 else ""
177 )
175 pinRetries, powerCycle = self.getPinRetries() 178 pinRetries, powerCycle = self.getPinRetries()
176 if pinRetries: 179 if pinRetries:
177 if powerCycle: 180 if powerCycle:
178 msg = self.tr( 181 msg = self.tr(
179 "PIN is temporarily blocked. Remove and re-insert the" 182 "PIN is temporarily blocked. Remove and re-insert the"
181 ) 184 )
182 else: 185 else:
183 msg = self.tr("%n attempts remaining", "", pinRetries) 186 msg = self.tr("%n attempts remaining", "", pinRetries)
184 else: 187 else:
185 msg = self.tr("PIN is blocked. The security key needs to be reset.") 188 msg = self.tr("PIN is blocked. The security key needs to be reset.")
189 if msg1:
190 msg += "\n" + msg1
186 else: 191 else:
187 msg = self.tr("A PIN has not been set.") 192 msg = self.tr("A PIN has not been set.")
188 data["pin"].append((self.tr("PIN"), msg)) 193 data["pin"].append((self.tr("PIN"), msg))
194
195 data["pin"].append(
196 (self.tr("Minimum PIN length"), str(self.__ctap2.info.min_pin_length))
197 )
189 198
190 alwaysUv = self.__ctap2.info.options.get("alwaysUv") 199 alwaysUv = self.__ctap2.info.options.get("alwaysUv")
191 msg = ( 200 msg = (
192 self.tr("not supported") 201 self.tr("not supported")
193 if alwaysUv is None 202 if alwaysUv is None
552 if self.__ctap2 is None: 561 if self.__ctap2 is None:
553 return None 562 return None
554 563
555 return self.__ctap2.info.options.get("clientPin") 564 return self.__ctap2.info.options.get("clientPin")
556 565
557 def forcedPinChange(self): 566 def pinChangeRequired(self):
558 """ 567 """
559 Public method to check for a forced PIN change. 568 Public method to check for a forced PIN change.
560 569
561 @return flag indicating a forced PIN change is required 570 @return flag indicating a forced PIN change is required
562 @rtype bool 571 @rtype bool
600 if self.__ctap2 is None or self.__clientPin is None: 609 if self.__ctap2 is None or self.__clientPin is None:
601 return False, self.tr("No security key connected.") 610 return False, self.tr("No security key connected.")
602 611
603 try: 612 try:
604 self.__clientPin.change_pin(old_pin=oldPin, new_pin=newPin) 613 self.__clientPin.change_pin(old_pin=oldPin, new_pin=newPin)
614 self.reconnectToDevice()
605 return True, self.tr("PIN was changed successfully.") 615 return True, self.tr("PIN was changed successfully.")
606 except CtapError as err: 616 except CtapError as err:
607 return ( 617 return (
608 False, 618 False,
609 self.tr("<p>Failed to change the PIN.</p><p>Reason: {0}</p>").format( 619 self.tr("<p>Failed to change the PIN.</p><p>Reason: {0}</p>").format(
623 if self.__ctap2 is None or self.__clientPin is None: 633 if self.__ctap2 is None or self.__clientPin is None:
624 return False, self.tr("No security key connected.") 634 return False, self.tr("No security key connected.")
625 635
626 try: 636 try:
627 self.__clientPin.set_pin(pin=pin) 637 self.__clientPin.set_pin(pin=pin)
638 self.reconnectToDevice()
628 return True, self.tr("PIN was set successfully.") 639 return True, self.tr("PIN was set successfully.")
629 except CtapError as err: 640 except CtapError as err:
630 return ( 641 return (
631 False, 642 False,
632 self.tr("<p>Failed to set the PIN.</p><p>Reason: {0}</p>").format( 643 self.tr("<p>Failed to set the PIN.</p><p>Reason: {0}</p>").format(
802 raise Fido2DeviceError( 813 raise Fido2DeviceError(
803 self.tr("Connected security key unplugged. Reinsert and try again.") 814 self.tr("Connected security key unplugged. Reinsert and try again.")
804 ) 815 )
805 816
806 return CredentialManagement(self.__ctap2, self.__clientPin.protocol, pinToken) 817 return CredentialManagement(self.__ctap2, self.__clientPin.protocol, pinToken)
818
819 ############################################################################
820 ## methods related to configuration handling
821 ############################################################################
822
823 def __initConfig(self, pin):
824 """
825 Private method to initialize a configuration object.
826
827 @param pin PIN to unlock the connected security key
828 @type str
829 @return reference to the configuration object
830 @rtype Config
831 @exception Fido2DeviceError raised to indicate an issue with the selected
832 security key
833 @exception Fido2PinError raised to indicate an issue with the PIN
834 """
835 if self.__clientPin is None:
836 self.__clientPin = ClientPin(self.__ctap2)
837
838 if pin == "":
839 pin = self.__pin
840 if pin is None:
841 # Error
842 raise Fido2PinError(
843 self.tr(
844 "The selected security key is not unlocked or no PIN was entered."
845 )
846 )
847
848 if not Config.is_supported(self.__ctap2.info):
849 raise Fido2DeviceError(
850 self.tr("The selected security key does not support configuration.")
851 )
852
853 try:
854 pinToken = self.__clientPin.get_pin_token(
855 pin, ClientPin.PERMISSION.AUTHENTICATOR_CFG
856 )
857 except CtapError as err:
858 raise Fido2PinError(
859 self.tr("PIN error: {0}").format(self.__pinErrorMessage(err))
860 )
861 except OSError:
862 raise Fido2DeviceError(
863 self.tr("Connected security key unplugged. Reinsert and try again.")
864 )
865
866 return Config(self.__ctap2, self.__clientPin.protocol, pinToken)
867
868 def forcePinChangeSupported(self):
869 """
870 Public method to check, if the 'forcePinChange' function is supported by the
871 selected security key.
872
873 @return flag indicating support
874 @rtype bool
875 """
876 if (
877 self.__ctap2 is None
878 or self.__ctap2.info is None
879 or not self.__ctap2.info.options.get("setMinPINLength")
880 ):
881 return False
882 else:
883 return True
884
885 def forcePinChange(self, pin):
886 """
887 Public method to force the PIN to be changed to a new value before use.
888
889 @param pin PIN to unlock the connected security key
890 @type str
891 """
892 config = self.__initConfig(pin)
893 config.set_min_pin_length(force_change_pin=True)
894 self.reconnectToDevice()
895
896 def canSetMinimumPinLength(self):
897 """
898 Public method to check, if the 'setMinPINLength' function is available.
899
900 @return flag indicating availability
901 @rtype bool
902 """
903 if (
904 self.__ctap2 is None
905 or self.__ctap2.info is None
906 or not self.__ctap2.info.options.get("setMinPINLength")
907 or (
908 self.__ctap2.info.options.get("alwaysUv")
909 and not self.__ctap2.info.options.get("clientPin")
910 )
911 ):
912 return False
913 else:
914 return True
915
916 def setMinimumPinLength(self, pin, minLength):
917 """
918 Public method to set the minimum PIN length.
919
920 @param pin PIN to unlock the connected security key
921 @type str
922 @param minLength minimum PIN length
923 @type int
924 """
925 if minLength < 4 or minLength > 63:
926 raise Fido2PinError(
927 self.tr("The minimum PIN length must be between 4 and 63.")
928 )
929 if minLength < self.__ctap2.info.min_pin_length:
930 raise Fido2PinError(
931 self.tr("The minimum PIN length must be at least {0}.").format(
932 self.__ctap2.info.min_pin_length
933 )
934 )
935
936 config = self.__initConfig(pin)
937 config.set_min_pin_length(min_pin_length=minLength)
938 self.reconnectToDevice()
939
940 def canToggleAlwaysUv(self):
941 """
942 Public method to check, if the 'toggleAlwaysUv' function is available.
943
944 @return flag indicating availability
945 @rtype bool
946 """
947 if (
948 self.__ctap2 is None
949 or self.__ctap2.info is None
950 or "alwaysUv" not in self.__ctap2.info.options
951 ):
952 return False
953 else:
954 return True
955
956 def getAlwaysUv(self):
957 """
958 Public method to get the value of the 'alwaysUv' flag of the current security
959 key.
960 """
961 if self.__ctap2 is None:
962 return False
963
964 info = self.__ctap2.get_info()
965 return info is not None and info.options.get("alwaysUv", False)
966
967 def toggleAlwaysUv(self, pin):
968 """
969 Public method to toggle the 'alwaysUv' flag of the selected security key.
970
971 @param pin PIN to unlock the connected security key
972 @type str
973 """
974 config = self.__initConfig(pin)
975 config.toggle_always_uv()
976 self.reconnectToDevice()

eric ide

mercurial