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() |