| # `biod`: Biometrics Daemon | |
| `biod` (Biometrics Daemon) is a daemon for enrolling, authenticating and | |
| managing biometrics data for faster unlocking. It manages all biometric sensors | |
| for all users of the device. | |
| [TOC] | |
| ## Keywords | |
| **Record**: specific piece of biometric data that a user registers along with | |
| its metadata. | |
| **Enroll**: the act of registering a record. | |
| **EnrollSession**: the session during which enrolling of a record happens. | |
| **Authenticate**: the act of checking a new piece of biometric data against | |
| stored records. | |
| **AuthSession**: the session during which authenticating a new piece of | |
| biometric data against stored records happens. | |
| **BiometricsManager**: manager for a piece of device/hardware/sensor for | |
| collecting a specific type of biometric data. Each `BiometricsManager` is in | |
| charge of storing the records that it enrolled. | |
| ## Building | |
| For more context, see the [Chromium OS Developer Guide]. | |
| * Start working on the `biod` package: | |
| ```bash | |
| (chroot) $ cros_workon-<board> start biod | |
| ``` | |
| * Build `biod`: | |
| ```bash | |
| (chroot) $ emerge-<board> biod | |
| ``` | |
| * Deploy `biod` to your `DUT`: | |
| ```bash | |
| (chroot) $ cros deploy ${DUT_IP_ADDR} biod | |
| ``` | |
| As a shortcut, you can also `scp` the `biod` binary to your DUT. | |
| ## Running | |
| `biod` is controlled by `upstart`. | |
| ### Start biod | |
| ```bash | |
| (dut)$ start biod | |
| ``` | |
| ### Stop biod | |
| ```bash | |
| (dut)$ stop biod | |
| ``` | |
| ### Check whether biod is running | |
| ```bash | |
| (dut)$ status biod | |
| ``` | |
| ## Logs | |
| `biod`'s logs are written to `/var/log/biod/biod.LATEST`. | |
| ## Unit Tests | |
| The unit tests can be run with the following command: | |
| ```bash | |
| (chroot)$ FEATURES=test emerge-<board> biod | |
| ``` | |
| ## Manual Testing | |
| You can add and remove fingerprints from the UI by navigating to | |
| `chrome://os-settings` in Chrome and searching for "fingerprint" in the search | |
| bar. Selecting the "Fingerprint settings" option will load the page for adding | |
| and removing fingerprints. | |
| ## Storage | |
| The records are stored under the directory: | |
| `/home/root/[hash_of_user_id]/biod/[name_of_biometrics_manager]/` with the file | |
| name: `Record[UUID]`. | |
| UUID has the form of `XXXXXXXX_XXXX_XXXX_XXXX_XXXXXXXXXXXX` where `X` represents | |
| a lowercase hex digit. UUID is a 128-bit random number generated with `guid`, so | |
| it is highly unlikely to repeat. `_` are used instead of `-` because this UUID | |
| is used in biod D-bus object paths, which do not allow `-`. | |
| Each record file is stored in JSON format, and contains one record and its | |
| record id and label. | |
| ## Firmware Updates | |
| The [`bio_fw_updater`] tool is responsible for updating the [FPMCU firmware]\ | |
| [automatically on boot]. The updater checks to see if the firmware binary in | |
| `/opt/google/biod/fw` matches the firmware that is flashed on the FPMCU and | |
| performs an update if the two do not match. Note that it does not consider | |
| whether the version of the firmware is semantically "newer"; it's strictly | |
| checking for an exact version match. | |
| To disable the automatic update, you can create the `.disable_fp_updater` file | |
| in the stateful partition: | |
| ```bash | |
| (dut) $ touch /var/lib/bio_fw_updater/.disable_fp_updater | |
| ``` | |
| For security reasons this will work only when we are allowed to boot developer | |
| mode from unsigned kernels (`crossystem dev_boot_signed_only` reports 0). | |
| When hardware and [software write protect] for the FPMCU are enabled, | |
| [`bio_fw_updater`] will only update the RW portion of the firmware. | |
| When devices are still under development, they will generally have | |
| [hardware write protect] enabled, but not software write protect. In this mode, | |
| [`bio_fw_updater`] will update both RO and RW. | |
| You can learn more about write protection and how to enable/disable in the | |
| [Firmware Write Protection] documentation. | |
| ### Logs | |
| [`bio_fw_updater`]'s logs are written to: | |
| * `/var/log/biod/bio_fw_updater.LATEST` | |
| * `/var/log/biod/bio_fw_updater.PREVIOUS` | |
| * `/var/log/biod/bio_fw_updater.<TIMESTAMP>` | |
| * `/var/log/bio_fw_updater.out` | |
| ## Hardware | |
| ### CrosFpBiometric | |
| CrosFpBiometric (fingerprint MCU) runs the firmware for image capture, matching | |
| and enrollment. Biod (`cros_fp_biometrics_manager.cc`) interacts with the MCU by | |
| doing system calls on `/dev/cros_fp`. | |
| #### Authentication | |
| On receiving an Authentication request, biod makes a ioctl call to put the MCU | |
| in FP_MODE_MATCH. It then listens for MBKP events from the MCU. On receiving the | |
| event, based on the result, biod either reports success or failure to the D-bus | |
| client (typically Chrome). | |
| Things get little complicated on devices with fingerprint overlapped on power | |
| button. On these devices, we need to be careful not to interpret user's | |
| interaction with power button as an intent to authenticate via fingerprint. To | |
| avoid such scenarios, we ignore fingerprint matches if we have seen a power | |
| button event in last few milliseconds. To achieve this, biod keeps track of | |
| power button events (`power_button_filter.cc`) by listening to d-bus events | |
| advertised by powerd (`power_manager.cc`). The sequence diagram below gives a | |
| sequence of events on devices with fingerprint overlapped on power button. | |
|  | |
| #### Enrollment | |
| <!-- mdformat off(b/139308852) --> | |
| *** note | |
| TODO | |
| *** | |
| <!-- mdformat on --> | |
| ## Chrome | |
| Biod communicates with Chrome via D-bus messages. Chrome provides the graphical | |
| interface for users to enroll new biometric data and manage their own records. | |
| Chrome is also responsible for the visual display during authentication. | |
| ## Login Manager | |
| When a user logs in or biod starts, biod will ask login manager for a list of | |
| currently logged-in users, and load from storage the records of the newly | |
| logged-in users. | |
| When a user logs out, all users log out at the same time, biod receives a signal | |
| from login manager and remove all records in the memory. | |
| ## Cryptohome | |
| Because the records are stored in per user stateful under | |
| `/home/root/[hash_of_user_id]`, they are naturally encrypted by cryptohome. | |
| Records are encrypted when the users log out and decrypted when the users log | |
| in. Cryptohome provides a layer of security to biod. | |
| ## Testing Tools | |
| ### biod_client_tool | |
| `biod_client_tool` provides the interface to fake the behavior of a biometrics | |
| client, for example, a lock screen or a biometric enrollment app. It can be used | |
| to test biod and biometric sensors. Use the `--help` flag to see the options. | |
| ## D-Bus API | |
| **Service Name**: `org.chromium.BiometricsDaemon` | |
| **Root Object**: `/org/chromium/BiometricsDaemon` | |
| The root object implements the `org.freedesktop.DBus.ObjectManager` interface | |
| which should be used to enumerate the available biometric devices. Each | |
| biometric device implements the | |
| `org.chromium.BiometricsDaemon.BiometricsManager` interface, which is used to | |
| retrieve previously made records or to start an `Enroll` or `Authenticate` | |
| session. `Enroll` sessions are for making new records. `Authenticate` sessions | |
| are for authenticating scanned biometric data against those previously made | |
| records. Each session object can be used to end the session, but signals still | |
| go through the BiometricsManager object to avoid a race condition between | |
| starting a session and connecting to the session's signals. Something to note | |
| about the session objects is that they will automatically be ended when the | |
| D-Bus client that created them (whomever called `StartEnrollSession` or | |
| `StartAuthSession`). This is to prevent the `BiometricsManager` from entering a | |
| stuck state in case the client crashes and nobody comes around to end the | |
| session. This shouldn't be an issue unless one expects the session to continue | |
| after the D-Bus client disconnects, for example while testing out `biod` using | |
| `dbus-send` or other single shot command line dbus tool. Each | |
| `BiometricsManager` can have only one session running concurrently. | |
| ### org.chromium.BiometricsDaemon.BiometricsManager | |
| #### StartEnrollSession (Method) | |
| ``` | |
| StartEnrollSession(in STRING user_id, in STRING label, out OBJECTPATH enroll_session) | |
| ``` | |
| * `user_id` refers to the sanitized user name returned by [SanitizeUserName] | |
| in libbrillo. | |
| * `label` is an arbitrary string chosen to be human readable by the user. | |
| * The returned `enroll_session` object path implements the | |
| `org.chromium.BiometricsDaemon.EnrollSession` interface, but | |
| `EnrollScanDone` and `SessionFailed` signals still come from this | |
| `BiometricsManager`. | |
| #### GetRecords (Method) | |
| ``` | |
| GetRecords(out ARRAY<OBJECTPATH> records) | |
| ``` | |
| Each returned object path implements the `org.chromium.BiometricsDaemon.Record` | |
| interface. | |
| #### DestroyAllRecords (Method) | |
| ``` | |
| DestroyAllRecords() | |
| ``` | |
| #### StartAuthSession (Method) | |
| ``` | |
| StartAuthSession(out OBJECTPATH auth_session) | |
| ``` | |
| The returned object path implements the | |
| `org.chromium.BiometricsDaemon.AuthSession` interface, but `AuthScanDone` and | |
| `SessionFailed` signals still come from this `BiometricsManager`. | |
| #### EnrollScanDone (Signal) | |
| ``` | |
| EnrollScanDone(UINT32 scan_result, BOOL complete) | |
| ``` | |
| If `complete` is true, the enrollment was successfully finished and saved. | |
| The `UINT32 scan_result` values are meant to be instructions to the user on how | |
| to get a better scan. They are as follows: | |
| Note: Pretty much ripped off from AOSP's `fingerprint_acquired_info` in | |
| [fingerprint.h](https://cs.android.com/android/platform/superproject/+/master:hardware/libhardware/include/hardware/fingerprint.h;l=67;drc=1ae7379baab4b2941ad1701190718cf53a051b48). | |
| Codes which are greater or equal to 10000 are ChromeOS specific codes. | |
| * 0 = Success (the sensor captured good data) | |
| * 1 = Partial (sensor needs more data) | |
| * 2 = Insufficient (too little detail for recognition) | |
| * 3 = Sensor Dirty | |
| * 4 = Too Slow (tell user to speed up) | |
| * 5 = Too Fast (tell user to slow down) | |
| * 6 = Immobile (tell user to move a little to get more data) | |
| * 10000 = No Match (tell user to use another finger, used during authentication) | |
| #### AuthScanDone (Signal) | |
| ``` | |
| AuthScanDone(FingerprintMessage message, std::unordered_map<std::string, std::vector<std::string>> matches) | |
| ``` | |
| The returned `message` is a protobuf which is the union of ScanResult enum and | |
| FingerprintError enum. ScanResult is used to give user a feedback about | |
| scanning. FingerprintError is used to provide more details when matching fails | |
| but is's not related to user. | |
| Note: Codes for FingerprintError comes from AOSP's `fingerprint_error` in | |
| [fingerprint.h](https://cs.android.com/android/platform/superproject/+/master:hardware/libhardware/include/hardware/fingerprint.h;l=48;drc=1ae7379baab4b2941ad1701190718cf53a051b48). | |
| Codes which are greater or equal to 10000 are ChromeOS specific codes. | |
| * 1 = HW Unavailable (general hardware error) | |
| * 2 = Unable To Process (operation can't continue) | |
| * 3 = Timeout (operation has timed out waiting for user input) | |
| * 4 = No Space (no space to store the template) | |
| * 5 = Canceled (operation can't proceed) | |
| * 6 = Unable To Remove (fingerprint with given ID can't be removed) | |
| * 7 = Lockout (hardware is locked due to too many attempts) | |
| * 10000 = No Templates (fingerprint hardware contains no templates) | |
| The returned `matches` are a map from user id to a list of biometric record ids. | |
| The user ids are the same ones given to `StartEnrollSession` in previous enroll | |
| sessions. Each user in the list have one or more records that indicate a match. | |
| Note that a piece of biometric data could be registered multiple times under the | |
| same or different users. | |
| #### SessionFailed (Signal) | |
| ``` | |
| SessionFailed() | |
| ``` | |
| General failure of enroll session and/or authenticate session that can not be | |
| recovered from | |
| #### StatusChanged (Signal) | |
| ``` | |
| StatusChanged(BiometricsManagerStatusChanged status) | |
| ``` | |
| The signal is emitted when status of Biometrics Manager changes. It contains | |
| a `status` protobuf which contains BiometricsManagerStatus enum. | |
| The enum contains following entries: | |
| * 1 = Initialized (ready for Enroll/Auth Session) | |
| #### Type (Property) | |
| ``` | |
| UINT32 Type | |
| ``` | |
| `Type` has one of the following values: | |
| * 0 = Unknown | |
| * 1 = Fingerprint | |
| ### org.chromium.BiometricsDaemon.AuthSession | |
| #### End (Method) | |
| ``` | |
| End() | |
| ``` | |
| Ends the authentication session and destroys this object path. Generally, the | |
| client should call this at some point because authentication sessions do not end | |
| on their own, unless there is some error. | |
| ### org.chromium.BiometricsDaemon.EnrollSession | |
| #### Cancel (Method) | |
| ``` | |
| Cancel() | |
| ``` | |
| Ends the enroll session without storing the enrollment and destroys this object | |
| path. Generally, the client **should not** call this as the Enroll session will | |
| end automatically once enough data is collected. Exceptions to this rule being | |
| that there was some error on the client side, the user explicitly canceled the | |
| session, or the client has determined the user to have given up, perhaps after | |
| some timeout has elapsed. | |
| ### org.chromium.BiometricsDaemon.Record | |
| #### Remove (Method) | |
| ``` | |
| Remove() | |
| ``` | |
| Deletes this record object from memory and persistent storage. It will no longer | |
| participate in any future Authenticate sessions. | |
| #### SetLabel (Method) | |
| ``` | |
| SetLabel(in STRING label) | |
| ``` | |
| Sets the human readable label of this `Record` object. | |
| #### Label (Property) | |
| ``` | |
| STRING Label | |
| ``` | |
| Read-only property that gets the previously set (by either `StartEnrollSession` | |
| or `SetLabel`) human readable label of this record object. | |
| ### Example API Usage | |
| The symbol **`<-`** means Chrome sends the command to | |
| `org.chromium.BiometricsDaemon` | |
| The symbol **`->`** is either a response or a signal from | |
| `org.chromium.BiometricsDaemon` | |
| 1. Logged in user clicks enroll in UI: | |
| ``` | |
| <- Object:/org/chromium/BiometricsDaemon Method:org.freedesktop.DBus.ObjectManager.GetManagedObjects | |
| -> [ | |
| "/org/chromium/BiometricsDaemon/BiometricsManager0" | |
| ] | |
| <- Object:/org/chromium/BiometricsDaemon/BiometricsManager0 Method:org.chromium.BiometricsDaemon.BiometricsManager.StartEnrollSession "<user id hash>" "Data 1" | |
| -> "/org/chromium/BiometricsDaemon/BiometricsManager0/EnrollSession" | |
| ``` | |
| 2. User presses finger onto sensor | |
| ``` | |
| -> org.chromium.BiometricsDaemon.BiometricsManager.EnrollScanDone (Success) false | |
| ``` | |
| 3. Chrome UI shows encouraging message about that scan to user | |
| 4. User presses finger again but too quickly | |
| ``` | |
| -> org.chromium.BiometricsDaemon.BiometricsManager.EnrollScanDone (Too Fast) false | |
| ``` | |
| 5. Chrome UI shows a stern message about how the user's finger is too fast. | |
| 6. [...] Continued until biod determines there is enough data | |
| ``` | |
| -> org.chromium.BiometricsDaemon.BiometricsManager.EnrollScanDone (Success) true | |
| ``` | |
| 7. Chrome displays successful enrollment message | |
| 8. Logged in user locks screen | |
| ``` | |
| <- Object:/org/chromium/BiometricsDaemon/BiometricsManager0 Method:org.chromium.BiometricsDaemon.BiometricsManager.StartAuthSession | |
| -> /org/chromium/BiometricsDaemon/BiometricsManager0/AuthSession | |
| ``` | |
| 9. User does a scan with dirty sensor and lock screen informs the user of this | |
| ``` | |
| -> org.chromium.BiometricsDaemon.BiometricsManager.AuthScanDone (Sensor Dirty) [empty array] | |
| ``` | |
| 10. User cleans sensor and tries again with success | |
| ``` | |
| -> org.chromium.BiometricsDaemon.BiometricsManager.AuthScanDone (Success) ["unordered_map<string user_id, vector<string record_id>>"] | |
| ``` | |
| 11. Lock screen lets user in | |
| [Chromium OS Developer Guide]: https://chromium.googlesource.com/chromiumos/docs/+/HEAD/developer_guide.md#Making-changes-to-packages-whose-source-code-is-checked-into-Chromium-OS-git-repositories | |
| [SanitizeUserName]: ../libbrillo/brillo/cryptohome.h | |
| [`bio_fw_updater`]: ./tools/bio_fw_updater.cc | |
| [FPMCU firmware]: https://chromium.googlesource.com/chromiumos/platform/ec/+/HEAD/docs/fingerprint/fingerprint.md | |
| [hardware write protect]: https://chromium.googlesource.com/chromiumos/platform/ec/+/HEAD/docs/write_protection.md#hw_wp | |
| [software write protect]: https://chromium.googlesource.com/chromiumos/platform/ec/+/HEAD/docs/write_protection.md#software-write-protect | |
| [automatically on boot]: ../init/upstart/boot-update-firmware.conf | |
| [Firmware Write Protection]: https://chromium.googlesource.com/chromiumos/platform/ec/+/HEAD/docs/write_protection.md |