diff --git a/README.md b/README.md
index 938e431..e986f92 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,16 @@
# python-validity
-Validity fingerprint sensor library.
-Originally designed to capture some of my findings for 138a:0097, but if you manage to get it working for some other Validity sensor - pull requests are welcome.
+Validity fingerprint sensor driver.
## Setting up
-On a Debian-based system, to re-install from sources (useful for testing):
+On Ubuntu system:
```
-./setup.py install --force --install-layout deb --prefix=/usr --root=/
+$ sudo apt remove fprintd
+$ sudo add-apt-repository ppa:uunicorn/open-fprintd
+$ sudo aptget update
+$ sudo apt install open-fprintd fprintd-clients python-validity
+...wait a bit...
+$ fprintd-enroll
```
## Initialization
diff --git a/bin/validity-sensors-firmware b/bin/validity-sensors-firmware
new file mode 100755
index 0000000..998017f
--- /dev/null
+++ b/bin/validity-sensors-firmware
@@ -0,0 +1,122 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# 2020 - Marco Trevisan
+#
+# Initializer for ThinkPad's validity sensors 138a:0090 and 138a:0097 and 06cb:009a
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+import argparse
+import os
+import subprocess
+import sys
+import tempfile
+import urllib.request
+import shutil
+
+from enum import Enum, auto
+from time import sleep
+from usb import core as usb_core
+
+python_validity_data='/usr/share/python-validity/'
+
+# FIXME: supported usb ids are duplicated in usb.py
+class VFS(Enum):
+ DEV_90 = (0x138a, 0x0090)
+ DEV_97 = (0x138a, 0x0097)
+ DEV_9a = (0x06cb, 0x009a)
+
+DEFAULT_URIS = {
+ VFS.DEV_90: {
+ 'driver': 'https://download.lenovo.com/pccbbs/mobiles/n1cgn08w.exe',
+ 'referral': 'https://support.lenovo.com/us/en/downloads/DS120491',
+ },
+ VFS.DEV_97: {
+ 'driver': 'https://download.lenovo.com/pccbbs/mobiles/nz3gf07w.exe',
+ 'referral': 'https://download.lenovo.com/pccbbs/mobiles/nz3gf07w.exe'
+ },
+ VFS.DEV_9a: {
+ 'driver': 'https://download.lenovo.com/pccbbs/mobiles/nz3gf07w.exe',
+ 'referral': 'https://download.lenovo.com/pccbbs/mobiles/nz3gf07w.exe'
+ }
+}
+
+# FIXME: filenames are duplicated in upload_fwext.py
+DEFAULT_FW_NAMES = {
+ VFS.DEV_90: '6_07f_Lenovo.xpfwext',
+ VFS.DEV_97: '6_07f_lenovo_mis_qm.xpfwext',
+ VFS.DEV_9a: '6_07f_lenovo_mis_qm.xpfwext'
+}
+
+
+def download_and_extract_fw(dev_type, fwdir, fwuri=None):
+ fwuri = fwuri if fwuri else DEFAULT_URIS[dev_type]['driver']
+ fwarchive = os.path.join(fwdir, 'fwinstaller.exe')
+ fwname = DEFAULT_FW_NAMES[dev_type]
+
+ print('Downloading {} to extract {}'.format(fwuri, fwname))
+
+ req = urllib.request.Request(fwuri)
+ req.add_header('Referer', DEFAULT_URIS[dev_type].get('referral', ''))
+ req.add_header('User-Agent', 'Mozilla/5.0 (X11; U; Linux)')
+
+ with urllib.request.urlopen(req) as response:
+ with open(fwarchive, 'wb') as out_file:
+ out_file.write(response.read())
+
+ subprocess.check_call(['innoextract',
+ '--output-dir', fwdir,
+ '--include', fwname,
+ '--collisions', 'overwrite',
+ fwarchive
+ ])
+
+ fwpath = subprocess.check_output([
+ 'find', fwdir, '-name', fwname]).decode('utf-8').strip()
+ print('Found firmware at {}'.format(fwpath))
+
+ if not fwpath:
+ raise Exception('No {} found in the archive'.format(fwname))
+
+ return fwpath
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--driver-uri')
+
+ args = parser.parse_args()
+
+ if os.geteuid() != 0:
+ raise Exception('This script needs to be executed as root')
+
+ dev_type = None
+ for d in VFS:
+ dev = usb_core.find(idVendor=d.value[0], idProduct=d.value[1])
+ if dev:
+ dev_type = d
+
+ if not dev_type:
+ raise Exception('No supported validity device found')
+
+ try:
+ subprocess.check_call(['innoextract', '--version'],
+ stdout=subprocess.DEVNULL)
+ except Exception as e:
+ print('Impossible to run innoextract: {}'.format(e))
+ sys.exit(1)
+
+ with tempfile.TemporaryDirectory() as fwdir:
+ fwpath = download_and_extract_fw(dev_type, fwdir, fwuri=args.driver_uri)
+ shutil.copy(fwpath, python_validity_data)
diff --git a/bin/validity-sensors-initializer b/bin/validity-sensors-initializer
deleted file mode 100755
index c588318..0000000
--- a/bin/validity-sensors-initializer
+++ /dev/null
@@ -1,239 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#
-# 2020 - Marco Trevisan
-#
-# Initializer for ThinkPad's validity sensors 138a:0090 and 138a:0097 and 06cb:009a
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
-import argparse
-import os
-import subprocess
-import sys
-import tempfile
-import urllib.request
-import shutil
-
-
-from enum import Enum, auto
-from time import sleep
-from usb import core as usb_core
-
-from validitysensor.flash import read_tls_flash
-from validitysensor.init_db import init_db
-from validitysensor.init_flash import init_flash
-from validitysensor.sensor import sensor as vfs_sensor
-from validitysensor.sensor import factory_reset
-from validitysensor.tls import tls as vfs_tls
-from validitysensor.upload_fwext import upload_fwext
-from validitysensor.usb import usb as vfs_usb
-
-class VFS(Enum):
- DEV_90 = (0x138a, 0x0090)
- DEV_97 = (0x138a, 0x0097)
- DEV_9a = (0x06cb, 0x009a)
-
-DEFAULT_URIS = {
- VFS.DEV_90: {
- 'driver': 'https://download.lenovo.com/pccbbs/mobiles/n1cgn08w.exe',
- 'referral': 'https://support.lenovo.com/us/en/downloads/DS120491',
- },
- VFS.DEV_97: {
- 'driver': 'https://download.lenovo.com/pccbbs/mobiles/nz3gf07w.exe',
- 'referral': 'https://download.lenovo.com/pccbbs/mobiles/nz3gf07w.exe'
- },
- VFS.DEV_9a: {
- 'driver': 'https://download.lenovo.com/pccbbs/mobiles/nz3gf07w.exe',
- 'referral': 'https://download.lenovo.com/pccbbs/mobiles/nz3gf07w.exe'
- }
-}
-
-DEFAULT_FW_NAMES = {
- VFS.DEV_90: '6_07f_Lenovo.xpfwext',
- VFS.DEV_97: '6_07f_lenovo_mis_qm.xpfwext',
- VFS.DEV_9a: '6_07f_lenovo_mis_qm.xpfwext'
-}
-
-
-class VFSInitializer():
- def __init__(self, args, usb_dev, dev_type):
- self.args = args
- self.usb_dev = usb_dev
- self.dev_type = dev_type
- self.dev_str = repr(usb_dev)
-
- print('Found device {}'.format(self.dev_str))
-
- if self.args.host_product or self.args.host_serial:
- vfs_tls.set_hwkey(product_name=self.args.host_product,
- serial_number=self.args.host_serial)
-
- def open_device(self, init=False):
- print('Opening device',hex(self.dev_type.value[1]))
- vfs_usb.open(vendor=self.dev_type.value[0], product=self.dev_type.value[1])
-
- if init:
- vfs_usb.send_init()
-
- # try to init TLS session from the flash
- vfs_tls.parseTlsFlash(read_tls_flash())
- vfs_tls.open()
-
- def restart(self):
- vfs_tls.reset()
- self.open_device(init=True)
-
- def download_and_extract_fw(self, fwdir, fwuri=None):
- fwuri = fwuri if fwuri else DEFAULT_URIS[self.dev_type]['driver']
- fwarchive = os.path.join(fwdir, 'fwinstaller.exe')
- fwname = DEFAULT_FW_NAMES[self.dev_type]
-
- print('Downloading {} to extract {}'.format(fwuri, fwname))
-
- req = urllib.request.Request(fwuri)
- req.add_header('Referer', DEFAULT_URIS[self.dev_type].get('referral', ''))
- req.add_header('User-Agent', 'Mozilla/5.0 (X11; U; Linux)')
-
- with urllib.request.urlopen(req) as response:
- with open(fwarchive, 'wb') as out_file:
- out_file.write(response.read())
-
- subprocess.check_call(['innoextract',
- '--output-dir', fwdir,
- '--include', fwname,
- '--collisions', 'overwrite',
- fwarchive
- ])
-
- fwpath = subprocess.check_output([
- 'find', fwdir, '-name', fwname]).decode('utf-8').strip()
- print('Found firmware at {}'.format(fwpath))
-
- if not fwpath:
- raise Exception('No {} found in the archive'.format(fwname))
-
- return fwpath
-
- def sleep(self, sec=3):
- print('Sleeping...')
- sleep(sec)
-
- def try_factory_reset(self):
- self.open_device()
- try:
- print('Factory reset...')
- factory_reset()
- except Exception as e:
- print('Factory reset failed with {}, this should not happen, but ' \
- 'we can ignore it, if pairing works...'.format(e))
-
- def pair(self):
- print('Pairing the sensor with device')
-
- max_retries = 5
- err = None
- for i in range(0, max_retries):
- try:
- self.open_device()
-
- print('Initializing flash...')
- init_flash()
- break
- except Exception as e:
- if err is None:
- err = e
- self.sleep()
- print('Try {} failed with error: {}'.format(i+1, e))
- finally:
- max_retries -= 1
-
- if max_retries == 0:
- print('Device didn\'t show up after reset, retry...')
- raise(err)
-
- self.sleep()
- self.restart()
-
- print('Uploading firmware...')
- upload_fwext()
-
- self.sleep()
- self.restart()
-
- print('Calibrating...')
- vfs_sensor.open()
- vfs_sensor.calibrate()
-
- print('Init database...')
- init_db()
-
- vfs_tls.reset()
-
- print('That\'s it, pairing with {} finished'.format(self.dev_str))
-
-
-if __name__ == "__main__":
- parser = argparse.ArgumentParser()
- parser.add_argument('--driver-uri')
- parser.add_argument('--firmware-path', type=argparse.FileType('r'))
- parser.add_argument('--calibration-data', type=argparse.FileType('r'))
- parser.add_argument('--host-product')
- parser.add_argument('--host-serial')
- parser.add_argument('--simulate-virtualbox', action='store_true')
- parser.add_argument('--download-fw-only', action='store_true')
-
- args = parser.parse_args()
-
- if args.simulate_virtualbox and (args.host_product or args.host_serial):
- parser.error("--simulate-virtualbox is incompatible with host params.")
-
- if os.geteuid() != 0:
- raise Exception('This script needs to be executed as root')
-
- usb_dev = None
- for d in VFS:
- dev = usb_core.find(idVendor=d.value[0], idProduct=d.value[1])
- if dev:
- dev_type = d
- usb_dev = dev
-
- if not usb_dev:
- raise Exception('No supported validity device found')
-
- try:
- subprocess.check_call(['innoextract', '--version'],
- stdout=subprocess.DEVNULL)
- except Exception as e:
- print('Impossible to run innoextract: {}'.format(e))
- sys.exit(1)
-
- vfs_initializer = VFSInitializer(args, usb_dev, dev_type)
-
- with tempfile.TemporaryDirectory() as fwdir:
- if args.firmware_path:
- fwpath = args.firmware_path.name
- else:
- fwpath = vfs_initializer.download_and_extract_fw(fwdir,
- fwuri=args.driver_uri)
-
- shutil.copy(fwpath, '/usr/share/python-validity/')
-
- if not args.download_fw_only:
- input('The device will be now reset to factory and associated to the ' \
- 'current laptop.\nPress Enter to continue (or Ctrl+C to cancel)...')
-
- vfs_initializer.try_factory_reset()
- vfs_initializer.sleep()
- vfs_initializer.pair()
diff --git a/dbus_service/dbus-service b/dbus_service/dbus-service
index 62ca9df..3c22625 100755
--- a/dbus_service/dbus-service
+++ b/dbus_service/dbus-service
@@ -2,6 +2,7 @@
import argparse
import re
+import sys
import dbus
import dbus.mainloop.glib
import dbus.service
@@ -15,7 +16,7 @@ from validitysensor.tls import tls
from validitysensor.usb import usb
from validitysensor.sid import sid_from_string
from validitysensor.db import subtype_to_string, db
-from validitysensor.sensor import sensor, reboot
+from validitysensor.sensor import sensor, reboot, RebootException
import pwd
GLib.threads_init()
@@ -166,14 +167,18 @@ if __name__ == '__main__':
usb.trace_enabled = True
tls.trace_enabled = True
- if args.devpath is None:
- init.open()
- else:
- z = re.match('^usb-(\d+)-(\d+)$', args.devpath)
- if not z:
- parser.error('Option --devpath should look like this: usb--')
+ try:
+ if args.devpath is None:
+ init.open()
+ else:
+ z = re.match('^usb-(\d+)-(\d+)$', args.devpath)
+ if not z:
+ parser.error('Option --devpath should look like this: usb--')
- init.open_devpath(*map(int, z.groups()))
+ init.open_devpath(*map(int, z.groups()))
+ except RebootException:
+ print('Initialization ended up in rebooting the sensor. Normal exit.')
+ sys.exit(0)
bus = dbus.SystemBus()
diff --git a/debian/python3-validity.postinst b/debian/python3-validity.postinst
index 0a27942..ca62874 100644
--- a/debian/python3-validity.postinst
+++ b/debian/python3-validity.postinst
@@ -4,6 +4,7 @@ set -e
#DEBHELPER#
if [ "$1" = "configure" ]; then
+ validity-sensors-firmware || true
systemctl daemon-reload || true
udevadm control --reload-rules || true
udevadm trigger || true
diff --git a/debian/python3-validity@.service b/debian/python3-validity@.service
index 03bf2fd..d69c127 100644
--- a/debian/python3-validity@.service
+++ b/debian/python3-validity@.service
@@ -5,5 +5,6 @@ Description=python-validity driver dbus service for %I
[Service]
Type=simple
ExecStart=/usr/lib/python-validity/dbus-service --devpath %i
+StandardOutput=syslog
Restart=no
diff --git a/scripts/factory-reset.py b/scripts/factory-reset.py
index 8589616..a997d50 100644
--- a/scripts/factory-reset.py
+++ b/scripts/factory-reset.py
@@ -1,6 +1,9 @@
from validitysensor.usb import usb
-from validitysensor.sensor import factory_reset
+from validitysensor.sensor import factory_reset, RebootException
-usb.open()
-factory_reset()
+try:
+ usb.open()
+ factory_reset()
+except RebootException:
+ pass
diff --git a/scripts/pair.py b/scripts/pair.py
deleted file mode 100644
index a16d694..0000000
--- a/scripts/pair.py
+++ /dev/null
@@ -1,44 +0,0 @@
-
-from time import sleep
-
-from validitysensor.usb import usb
-from validitysensor.tls import tls
-from validitysensor.flash import read_tls_flash
-from validitysensor.init_flash import init_flash
-from validitysensor.upload_fwext import upload_fwext
-from validitysensor.init_db import init_db
-from validitysensor.sensor import sensor, reboot
-
-#usb.trace_enabled=True
-#tls.trace_enabled=True
-
-def restart():
- print('Sleeping...')
- sleep(3)
- tls.reset()
- usb.open()
- usb.send_init()
- tls.parseTlsFlash(read_tls_flash())
- tls.open()
-
-try:
- usb.open()
- print('Initializing flash...')
- init_flash()
-
- restart()
- print('Uploading firmware...')
- upload_fwext()
-
- restart()
- print('Calibrating...')
- sensor.open(False)
- sensor.calibrate()
-
- print('Init database...')
- init_db()
-
- print('That\'s it, pairing\'s finished')
-finally:
- sleep(1)
- reboot()
diff --git a/setup.py b/setup.py
index 9309715..64bab60 100755
--- a/setup.py
+++ b/setup.py
@@ -8,7 +8,7 @@ setup(name='python-validity',
packages=['validitysensor'],
scripts=[
'bin/validity-led-dance',
- 'bin/validity-sensors-initializer',
+ 'bin/validity-sensors-firmware',
],
install_requires=[
'cryptography >= 2.1.4',
@@ -21,7 +21,6 @@ setup(name='python-validity',
'scripts/dbus-cmd.py',
'scripts/lsdbus.py',
'scripts/factory-reset.py',
- 'scripts/pair.py',
'scripts/prototype.py'
]),
]
diff --git a/validitysensor/flash.py b/validitysensor/flash.py
index 984efd8..ce4dad8 100644
--- a/validitysensor/flash.py
+++ b/validitysensor/flash.py
@@ -121,10 +121,10 @@ def write_flash_all(partition, ptr, buf):
write_flash(partition, ptr, chunk)
ptr += len(chunk)
-def read_flash_all(partition, start, end):
+def read_flash_all(partition, start, size):
bs = 0x1000
- blocks = [read_flash(partition, addr, bs) for addr in range(start, end, bs)]
- return b''.join(blocks)
+ blocks = [read_flash(partition, addr, bs) for addr in range(start, start+size, bs)]
+ return b''.join(blocks)[:size]
def write_fw_signature(partition, signature):
rsp=tls.cmd(pack(' 0:
- raise Exception('Flash is already partitioned')
-
cmd = unhex('4f 0000 0000')
cmd += with_hdr(0, serialize_flash_params(info.ic))
cmd += with_hdr(1, b''.join([serialize_partition(p) for p in layout]) + get_partition_signature())
@@ -108,6 +103,14 @@ def partition_flash(layout, client_public):
# ^ TODO - figure out what the rest of rsp means
def init_flash():
+ info = get_flash_info()
+
+ if len(info.partitions) > 0:
+ print('Flash has %d partitions.' % len(info.partitions))
+ return
+ else:
+ print('Flash was not initialized yet. Formatting...')
+
assert_status(usb.cmd(reset_blob))
skey = ec.generate_private_key(ec.SECP256R1(), crypto_backend)
@@ -115,7 +118,7 @@ def init_flash():
client_private = snums.private_value
client_public = snums.public_numbers
- partition_flash(flash_layout_hardcoded, client_public)
+ partition_flash(info, flash_layout_hardcoded, client_public)
rsp=usb.cmd(unhex('01'))
assert_status(rsp)
@@ -150,5 +153,6 @@ def init_flash():
# Persist certs and keys on cert partition.
write_flash(1, 0, tls.makeTlsFlash())
- # Reboot
+ # Reboot.
+ # The device will disconnect and our service will be started by udev as soon as it is connected again.
reboot()
diff --git a/validitysensor/sensor.py b/validitysensor/sensor.py
index 354b4a7..fa5a4e7 100644
--- a/validitysensor/sensor.py
+++ b/validitysensor/sensor.py
@@ -5,7 +5,7 @@ import os.path
from .tls import tls
from .usb import usb
from .db import db, subtype_to_string
-from .flash import write_enable, flush_changes, read_flash, erase_flash, write_flash_all
+from .flash import write_enable, flush_changes, read_flash, erase_flash, write_flash_all, read_flash_all
from time import sleep
from struct import pack, unpack
from .table_types import SensorTypeInfo, SensorCaptureProg
@@ -55,9 +55,12 @@ def write_hw_reg32(addr, val):
rsp=tls.cmd(pack('