def _calculate(self, credential, timestamp, password_key): dev = self._descriptor.open_device(TRANSPORT.CCID) controller = OathController(dev.driver) if controller.locked and password_key is not None: controller.validate(a2b_hex(password_key)) cred = controller.calculate(credential, timestamp) return cred
def _calculate_all(self, timestamp, password_key): dev = self._descriptor.open_device(TRANSPORT.CCID) controller = OathController(dev.driver) if controller.locked and password_key is not None: controller.validate(a2b_hex(password_key)) creds = controller.calculate_all(timestamp) creds = [c for c in creds if not c.hidden] return creds
def calculate(self, credential, timestamp): try: dev = self._descriptor.open_device(TRANSPORT.CCID) controller = OathController(dev.driver) self._unlock(controller) except Exception: return None code = controller.calculate(cred_from_dict(credential), timestamp) return code_to_dict(code)
def validate(self, key): dev = self._descriptor.open_device(TRANSPORT.CCID) controller = OathController(dev.driver) if key is not None: try: controller.validate(a2b_hex(key)) return True except: return False
def calculate(self, credential, timestamp, password_key): try: dev = self._descriptor.open_device(TRANSPORT.CCID) controller = OathController(dev.driver) if controller.locked and password_key is not None: controller.validate(a2b_hex(password_key)) except: return None return controller.calculate(Credential.from_dict(credential), timestamp).to_dict()
def refresh_credentials(self, timestamp, password_key=None): try: dev = self._descriptor.open_device(TRANSPORT.CCID) controller = OathController(dev.driver) if controller.locked and password_key is not None: controller.validate(a2b_hex(password_key)) creds = controller.calculate_all(timestamp) return [c.to_dict() for c in creds if not c.is_hidden()] except: return []
def refresh_credentials(self, timestamp): try: dev = self._descriptor.open_device(TRANSPORT.CCID) controller = OathController(dev.driver) self._unlock(controller) entries = controller.calculate_all(timestamp) return [pair_to_dict(cred, code) for (cred, code) in entries if not cred.is_hidden] except Exception: return []
def provide_password(self, password, remember=False): dev = self._descriptor.open_device(TRANSPORT.CCID) controller = OathController(dev.driver) self._key = controller.derive_key(password) try: controller.validate(self._key) except Exception: return False if remember: keys = self.settings.setdefault('keys', {}) keys[controller.id] = b2a_hex(self._key).decode() self.settings.write() return True
def needs_validation(self): try: dev = self._descriptor.open_device(TRANSPORT.CCID) controller = OathController(dev.driver) return controller.locked except: return False
def get_oath_id(self): try: dev = self._descriptor.open_device(TRANSPORT.CCID) controller = OathController(dev.driver) except: return None return b2a_hex(controller.id).decode('utf-8')
def set_password(self, new_password, remember=False): dev = self._descriptor.open_device(TRANSPORT.CCID) controller = OathController(dev.driver) self._unlock(controller) keys = self.settings.setdefault('keys', {}) if new_password is not None: self._key = controller.set_password(new_password) if remember: keys[controller.id] = b2a_hex(self._key).decode() elif controller.id in keys: del keys[controller.id] else: controller.clear_password() del keys[controller.id] self._key = None self.settings.write()
def refresh(self, otp_mode=False): descriptors = get_descriptors() if len(descriptors) != 1: self._descriptor = None return None desc = descriptors[0] if desc.fingerprint != ( self._descriptor.fingerprint if self._descriptor else None) \ or not otp_mode and not self._dev_info.get('version'): try: dev = desc.open_device( TRANSPORT.OTP if otp_mode else TRANSPORT.CCID) if otp_mode: version = None else: controller = OathController(dev.driver) version = controller.version except Exception: return None self._descriptor = desc self._dev_info = { 'name': dev.device_name, 'version': version, 'serial': dev.serial or '', 'enabled': [c.name for c in CAPABILITY if c & dev.enabled], 'connections': [t.name for t in TRANSPORT if t & dev.capabilities] } return self._dev_info
def set_password(self, new_password, password_key): dev = self._descriptor.open_device(TRANSPORT.CCID) controller = OathController(dev.driver) if controller.locked and password_key is not None: controller.validate(a2b_hex(password_key)) if new_password is not None: key = derive_key(controller.id, new_password) controller.set_password(key) else: controller.clear_password()
def refresh_devices(self, otp_mode=False, reader_filter=None): self._devices = [] if not otp_mode and reader_filter: self._reader_filter = reader_filter dev = self._get_dev_from_reader() if dev: if is_nfc(self._reader_filter): selectable = nfc_selectable(dev) else: selectable = usb_selectable(dev, otp_mode) if selectable: controller = OathController(dev.driver) has_password = controller.locked else: has_password = False self._devices.append({ 'name': dev.device_name, 'version': '.'.join(str(x) for x in dev.version) if dev.version else '', 'serial': dev.serial or '', 'usbInterfacesEnabled': str(dev.mode).split('+'), 'hasPassword': has_password, 'selectable': selectable, 'validated': True }) return success({'devices': self._devices}) else: return success({'devices': []}) else: self._reader_filter = None # Forget current serial and derived key if no descriptors # Return empty list of devices if not self._descs: self._current_serial = None self._current_derived_key = None return success({'devices': []}) self._devices = self._get_devices(otp_mode) # If no current serial, or current serial seems removed, # select the first serial found. if not self._current_serial or (self._current_serial not in [ dev['serial'] for dev in self._devices ]): for dev in self._devices: if dev['serial']: self._current_serial = dev['serial'] break return success({'devices': self._devices})
def add_credential(self, name, secret, issuer, oath_type, algo, digits, period, touch): dev = self._descriptor.open_device(TRANSPORT.CCID) controller = OathController(dev.driver) self._unlock(controller) try: secret = parse_b32_key(secret) except Exception as e: return str(e) try: controller.put( CredentialData(secret, issuer, name, OATH_TYPE[oath_type], ALGO[algo], int(digits), int(period), 0, touch)) except APDUError as e: # NEO doesn't return a no space error if full, # but a command aborted error. Assume it's because of # no space in this context. if e.sw in (SW.NO_SPACE, SW.COMMAND_ABORTED): return 'No space' else: raise
def refresh_devices(self, otp_mode=False, reader_filter=None): self._devices = [] if not otp_mode and reader_filter: self._reader_filter = reader_filter dev = self._get_dev_from_reader() if dev: if is_nfc(self._reader_filter): selectable = nfc_selectable(dev) else: selectable = usb_selectable(dev, otp_mode) if selectable: controller = OathController(dev.driver) has_password = controller.locked else: has_password = False self._devices.append( { "name": dev.device_name, "version": ".".join(str(x) for x in dev.version) if dev.version else "", "serial": dev.serial or "", "usbInterfacesEnabled": str(dev.mode).split("+"), "hasPassword": has_password, "selectable": selectable, "validated": True, } ) return success({"devices": self._devices}) else: return success({"devices": []}) else: self._reader_filter = None # Forget current serial and derived key if no descriptors # Return empty list of devices if not self._descs: self._current_serial = None self._current_derived_key = None return success({"devices": []}) self._devices = self._get_devices(otp_mode) # If no current serial, or current serial seems removed, # select the first serial found. if not self._current_serial or ( self._current_serial not in [dev["serial"] for dev in self._devices] ): for dev in self._devices: if dev["serial"]: self._current_serial = dev["serial"] break return success({"devices": self._devices})
def refresh(self, otp_mode=False): descriptors = get_descriptors() if len(descriptors) != 1: self._descriptor = None return None desc = descriptors[0] unmatched_otp_mode = otp_mode and not desc.mode.has_transport( TRANSPORT.OTP) unmatched_ccid_mode = not otp_mode and not desc.mode.has_transport( TRANSPORT.CCID) if unmatched_otp_mode or unmatched_ccid_mode: return { 'transports': [t.name for t in TRANSPORT.split(desc.mode.transports)], 'usable': False, } if desc.fingerprint != ( self._descriptor.fingerprint if self._descriptor else None) \ or not otp_mode and not self._dev_info.get('version'): try: dev = desc.open_device( TRANSPORT.OTP if otp_mode else TRANSPORT.CCID) if otp_mode: version = None else: controller = OathController(dev.driver) version = controller.version except Exception as e: logger.debug('Failed to refresh YubiKey', exc_info=e) return None self._descriptor = desc self._dev_info = { 'usable': True, 'name': dev.device_name, 'version': version, 'serial': dev.serial or '', 'usb_interfaces_supported': [t.name for t in TRANSPORT if t & dev.config.usb_supported], 'usb_interfaces_enabled': str(dev.mode).split('+') } return self._dev_info
def add_credential(self, name, key, oath_type, digits, algo, touch, password_key): dev = self._descriptor.open_device(TRANSPORT.CCID) controller = OathController(dev.driver) if controller.locked and password_key is not None: controller.validate(a2b_hex(password_key)) try: key = parse_b32_key(key) except Exception as e: return str(e) try: controller.put(key, name, oath_type, digits, algo=algo, require_touch=touch) except APDUError as e: # NEO doesn't return a no space error if full, # but a command aborted error. Assume it's because of # no space in this context. if e.sw == SW.NO_SPACE or e.sw == SW.COMMAND_ABORTED: return 'No space' else: raise
def add_credential( self, name, secret, issuer, oath_type, algo, digits, period, touch): dev = self._descriptor.open_device(TRANSPORT.CCID) controller = OathController(dev.driver) self._unlock(controller) try: secret = parse_b32_key(secret) except Exception as e: return str(e) try: controller.put(CredentialData( secret, issuer, name, OATH_TYPE[oath_type], ALGO[algo], int(digits), int(period), 0, touch )) except APDUError as e: # NEO doesn't return a no space error if full, # but a command aborted error. Assume it's because of # no space in this context. if e.sw in (SW.NO_SPACE, SW.COMMAND_ABORTED): return 'No space' else: raise
def _get_devices(self, otp_mode=False): res = [] descs_to_match = self._descs[:] handled_serials = set() time.sleep(0.5) # Let macOS take time to see the reader for transport in [TRANSPORT.CCID, TRANSPORT.OTP, TRANSPORT.FIDO]: if not descs_to_match: return res for dev in list_devices(transport): if not descs_to_match: return res serial = dev.serial selectable = usb_selectable(dev, otp_mode) if selectable and not otp_mode and transport == TRANSPORT.CCID: controller = OathController(dev.driver) has_password = controller.locked else: has_password = False if serial not in handled_serials: handled_serials.add(serial) matches = [ d for d in descs_to_match if (d.key_type, d.mode) == (dev.driver.key_type, dev.driver.mode) ] if len(matches) > 0: matching_descriptor = matches[0] res.append({ 'name': dev.device_name, 'version': '.'.join( str(x) for x in dev.version) if dev.version else '', 'serial': serial or '', 'usbInterfacesEnabled': str(dev.mode).split('+'), 'hasPassword': has_password, 'selectable': selectable, 'validated': not has_password }) descs_to_match.remove(matching_descriptor) return res
def add_credential(self, name, key, oath_type, digits, algo, touch, password_key): dev = self._descriptor.open_device(TRANSPORT.CCID) controller = OathController(dev.driver) if controller.locked and password_key is not None: controller.validate(a2b_hex(password_key)) try: key = parse_b32_key(key) except Exception as e: return str(e) controller.put(key, name, oath_type, digits, algo=algo, require_touch=touch)
def derive_key(self, password): dev = self._descriptor.open_device(TRANSPORT.CCID) controller = OathController(dev.driver) key = derive_key(controller.id, password) return b2a_hex(key).decode('utf-8')
def reset(self): dev = self._descriptor.open_device(TRANSPORT.CCID) controller = OathController(dev.driver) controller.reset()
def delete_credential(self, credential): dev = self._descriptor.open_device(TRANSPORT.CCID) controller = OathController(dev.driver) self._unlock(controller) controller.delete(cred_from_dict(credential))
def delete_credential(self, credential, password_key): dev = self._descriptor.open_device(TRANSPORT.CCID) controller = OathController(dev.driver) if controller.locked and password_key is not None: controller.validate(a2b_hex(password_key)) controller.delete(Credential.from_dict(credential))
def _get_devices(self, otp_mode=False): res = [] descs_to_match = self._descs[:] handled_serials = set() time.sleep(0.5) # Let macOS take time to see the reader for transport in [TRANSPORT.CCID, TRANSPORT.OTP, TRANSPORT.FIDO]: if not descs_to_match: return res for dev in list_devices(transport): if not descs_to_match: return res serial = dev.serial selectable = usb_selectable(dev, otp_mode) if dev.version: version = ".".join(str(x) for x in dev.version) else: version = "" if selectable and not otp_mode and transport == TRANSPORT.CCID: controller = OathController(dev.driver) has_password = controller.locked else: has_password = False if serial not in handled_serials: handled_serials.add(serial) matches_all = [ d for d in self._descs[:] if (d.key_type, d.mode) == (dev.driver.key_type, dev.driver.mode) ] matches_left = [ d for d in descs_to_match if (d.key_type, d.mode) == (dev.driver.key_type, dev.driver.mode) ] if len(matches_left) > 0: if len(matches_all) == 1 and version == "": # Only one matching descriptor of all descriptors, # try reading any missing version from it descriptor = matches_all[0] if descriptor.version: version = ".".join( str(x) for x in descriptor.version ) res.append( { "name": dev.device_name, "version": version, "serial": serial or "", "usbInterfacesEnabled": str(dev.mode).split("+"), "hasPassword": has_password, "selectable": selectable, "validated": not has_password, } ) descs_to_match.remove(matches_left[0]) return res
def get_oath_id(self): try: dev = self._descriptor.open_device(TRANSPORT.CCID) return OathController(dev.driver).id except Exception: return None
def needs_validation(self): try: dev = self._descriptor.open_device(TRANSPORT.CCID) return not self._unlock(OathController(dev.driver)) except Exception: return True
def __enter__(self): return OathController(self._dev.driver)