/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * This is a very basic implementation of fingerprint to allow testing on the emulator. It * is *not* meant to be the final implementation on real devices. For example, it does *not* * implement all of the required features, such as secure template storage and recognition * inside a Trusted Execution Environment (TEE). However, this file is a reasonable starting * point as developers add fingerprint support to their platform. See inline comments and * recommendations for details. * * Please see the Android Compatibility Definition Document (CDD) for a full list of requirements * and suggestions. */ #define LOG_TAG "FingerprintHal" #include #include #include #include #include #include #include #include #include #include #define FINGERPRINT_LISTEN_SERVICE_NAME "fingerprintlisten" #define FINGERPRINT_FILENAME \ "/data/system/users/0/fpdata/emulator_fingerprint_storage.bin" #define MAX_COMM_CHARS 128 #define MAX_COMM_ERRORS 8 // Typical devices will allow up to 5 fingerprints per user to maintain performance of // t < 500ms for recognition. This is the total number of fingerprints we'll store. #define MAX_NUM_FINGERS 20 #define MAX_FID_VALUE 0x7FFFFFFF // Arbitrary limit /** * Most devices will have an internal state machine resembling this. There are 3 basic states, as * shown below. When device is not authenticating or enrolling, it is expected to be in * the idle state. * * Note that this is completely independent of device wake state. If the hardware device was in * the "scan" state when the device drops into power collapse, it should resume scanning when power * is restored. This is to facilitate rapid touch-to-unlock from keyguard. */ typedef enum worker_state_t { STATE_IDLE = 0, STATE_ENROLL, STATE_SCAN, STATE_EXIT } worker_state_t; typedef struct worker_thread_t { pthread_t thread; worker_state_t state; uint64_t secureid[MAX_NUM_FINGERS]; uint64_t authenid[MAX_NUM_FINGERS]; } worker_thread_t; typedef struct qemu_fingerprint_device_t { fingerprint_device_t device; // "inheritance" worker_thread_t listener; uint64_t op_id; uint64_t challenge; uint64_t user_id; uint64_t group_id; uint64_t secure_user_id; uint64_t authenticator_id; int qchanfd; pthread_mutex_t lock; } qemu_fingerprint_device_t; /******************************************************************************/ static void saveFingerprint(worker_thread_t* listener, int idx) { ALOGD("----------------> %s -----------------> idx %d", __FUNCTION__, idx); // Save fingerprints to file FILE* fp = fopen(FINGERPRINT_FILENAME, "r+"); // write but don't truncate if (fp == NULL) { ALOGE("Could not open fingerprints storage at %s; " "fingerprints won't be saved", FINGERPRINT_FILENAME); perror("Failed to open file"); return; } ALOGD("Write fingerprint[%d] (0x%" PRIx64 ",0x%" PRIx64 ")", idx, listener->secureid[idx], listener->authenid[idx]); if (fseek(fp, (idx) * sizeof(uint64_t), SEEK_SET) < 0) { ALOGE("Failed while seeking for fingerprint[%d] in emulator storage", idx); fclose(fp); return; } int ns = fwrite(&listener->secureid[idx], sizeof(uint64_t), 1, fp); if (fseek(fp, (MAX_NUM_FINGERS + idx) * sizeof(uint64_t), SEEK_SET) < 0) { ALOGE("Failed while seeking for fingerprint[%d] in emulator storage", idx); fclose(fp); return; } int na = fwrite(&listener->authenid[idx], sizeof(uint64_t), 1, fp); if (ns != 1 || na != 1) ALOGW("Corrupt emulator fingerprints storage; could not save " "fingerprints"); fclose(fp); return; } static void loadFingerprints(worker_thread_t* listener) { ALOGD("----------------> %s ----------------->", __FUNCTION__); FILE* fp = fopen(FINGERPRINT_FILENAME, "a+"); // so we can create if empty if (fp == NULL) { ALOGE("Could not load fingerprints from storage at %s; " "it has not yet been created.", FINGERPRINT_FILENAME); perror("Failed to open/create file"); return; } int ns = fread(listener->secureid, MAX_NUM_FINGERS * sizeof(uint64_t), 1, fp); int na = fread(listener->authenid, MAX_NUM_FINGERS * sizeof(uint64_t), 1, fp); if (ns != 1 || na != 1) ALOGW("Corrupt emulator fingerprints storage (read %d+%db)", ns, na); int i = 0; for (i = 0; i < MAX_NUM_FINGERS; i++) ALOGD("Read fingerprint %d (0x%" PRIx64 ",0x%" PRIx64 ")", i, listener->secureid[i], listener->authenid[i]); fclose(fp); return; } /******************************************************************************/ static uint64_t get_64bit_rand() { // This should use a cryptographically-secure random number generator like arc4random(). // It should be generated inside of the TEE where possible. Here we just use something // very simple. ALOGD("----------------> %s ----------------->", __FUNCTION__); uint64_t r = (((uint64_t)rand()) << 32) | ((uint64_t)rand()); return r != 0 ? r : 1; } static uint64_t fingerprint_get_auth_id(struct fingerprint_device* device) { // This should return the authentication_id generated when the fingerprint template database // was created. Though this isn't expected to be secret, it is reasonable to expect it to be // cryptographically generated to avoid replay attacks. qemu_fingerprint_device_t* qdev = (qemu_fingerprint_device_t*)device; ALOGD("----------------> %s ----------------->", __FUNCTION__); uint64_t authenticator_id = 0; pthread_mutex_lock(&qdev->lock); authenticator_id = qdev->authenticator_id; pthread_mutex_unlock(&qdev->lock); return authenticator_id; } static int fingerprint_set_active_group(struct fingerprint_device __unused *device, uint32_t gid, const char *path) { // Groups are a future feature. For now, the framework sends the profile owner's id (userid) // as the primary group id for the user. This code should create a tuple (groupId, fingerId) // that represents a single fingerprint entity in the database. For now we just generate // globally unique ids. ALOGW("Setting active finger group not implemented"); return 0; } /** * If fingerprints are enrolled, then this function is expected to put the sensor into a * "scanning" state where it's actively scanning and recognizing fingerprint features. * Actual authentication must happen in TEE and should be monitored in a separate thread * since this function is expected to return immediately. */ static int fingerprint_authenticate(struct fingerprint_device *device, uint64_t operation_id, __unused uint32_t gid) { qemu_fingerprint_device_t* qdev = (qemu_fingerprint_device_t*)device; pthread_mutex_lock(&qdev->lock); qdev->op_id = operation_id; qdev->listener.state = STATE_SCAN; pthread_mutex_unlock(&qdev->lock); return 0; } /** * This is expected to put the sensor into an "enroll" state where it's actively scanning and * working towards a finished fingerprint database entry. Authentication must happen in * a separate thread since this function is expected to return immediately. * * Note: This method should always generate a new random authenticator_id. * * Note: As with fingerprint_authenticate(), this would run in TEE on a real device. */ static int fingerprint_enroll(struct fingerprint_device *device, const hw_auth_token_t *hat, uint32_t __unused gid, uint32_t __unused timeout_sec) { ALOGD("fingerprint_enroll"); qemu_fingerprint_device_t* dev = (qemu_fingerprint_device_t*)device; if (!hat) { ALOGW("%s: null auth token", __func__); return -EPROTONOSUPPORT; } if (hat->challenge == dev->challenge) { // The secure_user_id retrieved from the auth token should be stored // with the enrolled fingerprint template and returned in the auth result // for a successful authentication with that finger. dev->secure_user_id = hat->user_id; } else { ALOGW("%s: invalid auth token", __func__); } if (hat->version != HW_AUTH_TOKEN_VERSION) { return -EPROTONOSUPPORT; } if (hat->challenge != dev->challenge && !(hat->authenticator_type & HW_AUTH_FINGERPRINT)) { return -EPERM; } dev->user_id = hat->user_id; pthread_mutex_lock(&dev->lock); dev->listener.state = STATE_ENROLL; pthread_mutex_unlock(&dev->lock); // fingerprint id, authenticator id, and secure_user_id // will be stored by worked thread return 0; } /** * The pre-enrollment step is simply to get an authentication token that can be wrapped and * verified at a later step. The primary purpose is to return a token that protects against * spoofing and replay attacks. It is passed to password authentication where it is wrapped and * propagated to the enroll step. */ static uint64_t fingerprint_pre_enroll(struct fingerprint_device *device) { ALOGD("----------------> %s ----------------->", __FUNCTION__); uint64_t challenge = 0; qemu_fingerprint_device_t* qdev = (qemu_fingerprint_device_t*)device; // The challenge will typically be a cryptographically-secure key // coming from the TEE so it can be verified at a later step. For now we just generate a // random value. challenge = get_64bit_rand(); pthread_mutex_lock(&qdev->lock); qdev->challenge = challenge; pthread_mutex_unlock(&qdev->lock); return challenge; } static int fingerprint_post_enroll(struct fingerprint_device* device) { ALOGD("----------------> %s ----------------->", __FUNCTION__); qemu_fingerprint_device_t* qdev = (qemu_fingerprint_device_t*)device; pthread_mutex_lock(&qdev->lock); qdev->challenge = 0; pthread_mutex_unlock(&qdev->lock); return 0; } /** * Cancel is called by the framework to cancel an outstanding event. This should *not* be called * by the driver since it will cause the framework to stop listening for fingerprints. */ static int fingerprint_cancel(struct fingerprint_device *device) { ALOGD("----------------> %s ----------------->", __FUNCTION__); qemu_fingerprint_device_t* qdev = (qemu_fingerprint_device_t*)device; fingerprint_msg_t msg = {0}; msg.type = FINGERPRINT_ERROR; msg.data.error = FINGERPRINT_ERROR_CANCELED; pthread_mutex_lock(&qdev->lock); qdev->listener.state = STATE_IDLE; pthread_mutex_unlock(&qdev->lock); device->notify(&msg); return 0; } static int fingerprint_enumerate(struct fingerprint_device *device, fingerprint_finger_id_t *results, uint32_t *max_size) { ALOGD("----------------> %s ----------------->", __FUNCTION__); if (device == NULL || results == NULL || max_size == NULL) { ALOGE("Cannot enumerate saved fingerprints with uninitialized params"); return -1; } qemu_fingerprint_device_t* qdev = (qemu_fingerprint_device_t*)device; unsigned int i = 0; int num = 0; for (i = 0; i < MAX_NUM_FINGERS; i++) { if (qdev->listener.secureid[i] != 0 || qdev->listener.authenid[i] != 0) { ALOGD("ENUM: Fingerprint [%d] = 0x%" PRIx64 ",%" PRIx64, i, qdev->listener.secureid[i], qdev->listener.authenid[i]); num++; } } return num; } static int fingerprint_remove(struct fingerprint_device *device, uint32_t __unused gid, uint32_t fid) { int idx = 0; fingerprint_msg_t msg = {0}; ALOGD("----------------> %s -----------------> fid %d", __FUNCTION__, fid); if (device == NULL) { ALOGE("Can't remove fingerprint (gid=%d, fid=%d); " "device not initialized properly", gid, fid); return -1; } qemu_fingerprint_device_t* qdev = (qemu_fingerprint_device_t*)device; if (fid == 0) { // Delete all fingerprints // I'll do this one at a time, so I am not // holding the mutext during the notification bool listIsEmpty; do { pthread_mutex_lock(&qdev->lock); listIsEmpty = true; // Haven't seen a valid entry yet for (idx = 0; idx < MAX_NUM_FINGERS; idx++) { uint32_t theFid = qdev->listener.authenid[idx]; if (theFid != 0) { // Delete this entry qdev->listener.secureid[idx] = 0; qdev->listener.authenid[idx] = 0; saveFingerprint(&qdev->listener, idx); // Send a notification that we deleted this one pthread_mutex_unlock(&qdev->lock); msg.type = FINGERPRINT_TEMPLATE_REMOVED; msg.data.removed.finger.fid = theFid; device->notify(&msg); // Because we released the mutex, the list // may have changed. Restart the 'for' loop // after reacquiring the mutex. listIsEmpty = false; break; } } // end for (idx < MAX_NUM_FINGERS) } while (!listIsEmpty); qdev->listener.state = STATE_IDLE; pthread_mutex_unlock(&qdev->lock); } else { // Delete one fingerprint // Look for this finger ID in our table. pthread_mutex_lock(&qdev->lock); for (idx = 0; idx < MAX_NUM_FINGERS; idx++) { if (qdev->listener.authenid[idx] == fid && qdev->listener.secureid[idx] != 0) { // Found it! break; } } if (idx >= MAX_NUM_FINGERS) { qdev->listener.state = STATE_IDLE; pthread_mutex_unlock(&qdev->lock); ALOGE("Fingerprint ID %d not found", fid); return FINGERPRINT_ERROR; } qdev->listener.secureid[idx] = 0; qdev->listener.authenid[idx] = 0; saveFingerprint(&qdev->listener, idx); qdev->listener.state = STATE_IDLE; pthread_mutex_unlock(&qdev->lock); msg.type = FINGERPRINT_TEMPLATE_REMOVED; msg.data.removed.finger.fid = fid; device->notify(&msg); } return 0; } static int set_notify_callback(struct fingerprint_device *device, fingerprint_notify_t notify) { ALOGD("----------------> %s ----------------->", __FUNCTION__); if (device == NULL || notify == NULL) { ALOGE("Failed to set notify callback @ %p for fingerprint device %p", device, notify); return -1; } qemu_fingerprint_device_t* qdev = (qemu_fingerprint_device_t*)device; pthread_mutex_lock(&qdev->lock); qdev->listener.state = STATE_IDLE; device->notify = notify; pthread_mutex_unlock(&qdev->lock); ALOGD("fingerprint callback notification set"); return 0; } static void send_scan_notice(qemu_fingerprint_device_t* qdev, int fid) { ALOGD("----------------> %s ----------------->", __FUNCTION__); // acquired message fingerprint_msg_t acqu_msg = {0}; acqu_msg.type = FINGERPRINT_ACQUIRED; acqu_msg.data.acquired.acquired_info = FINGERPRINT_ACQUIRED_GOOD; // authenticated message fingerprint_msg_t auth_msg = {0}; auth_msg.type = FINGERPRINT_AUTHENTICATED; auth_msg.data.authenticated.finger.fid = fid; auth_msg.data.authenticated.finger.gid = 0; // unused auth_msg.data.authenticated.hat.version = HW_AUTH_TOKEN_VERSION; auth_msg.data.authenticated.hat.authenticator_type = htobe32(HW_AUTH_FINGERPRINT); auth_msg.data.authenticated.hat.challenge = qdev->op_id; auth_msg.data.authenticated.hat.authenticator_id = qdev->authenticator_id; auth_msg.data.authenticated.hat.user_id = qdev->secure_user_id; struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); auth_msg.data.authenticated.hat.timestamp = htobe64((uint64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000); // pthread_mutex_lock(&qdev->lock); qdev->device.notify(&acqu_msg); qdev->device.notify(&auth_msg); // pthread_mutex_unlock(&qdev->lock); return; } static void send_enroll_notice(qemu_fingerprint_device_t* qdev, int fid) { ALOGD("----------------> %s -----------------> fid %d", __FUNCTION__, fid); if (fid == 0) { ALOGD("Fingerprint ID is zero (invalid)"); return; } if (qdev->secure_user_id == 0) { ALOGD("Secure user ID is zero (invalid)"); return; } // Find an available entry in the table pthread_mutex_lock(&qdev->lock); int idx = 0; for (idx = 0; idx < MAX_NUM_FINGERS; idx++) { if (qdev->listener.secureid[idx] == 0 || qdev->listener.authenid[idx] == 0) { // This entry is available break; } } if (idx >= MAX_NUM_FINGERS) { qdev->listener.state = STATE_SCAN; pthread_mutex_unlock(&qdev->lock); ALOGD("Fingerprint ID table is full"); return; } qdev->listener.secureid[idx] = qdev->secure_user_id; qdev->listener.authenid[idx] = fid; saveFingerprint(&qdev->listener, idx); qdev->listener.state = STATE_SCAN; pthread_mutex_unlock(&qdev->lock); // LOCKED notification? fingerprint_msg_t msg = {0}; msg.type = FINGERPRINT_TEMPLATE_ENROLLING; msg.data.enroll.finger.fid = fid; msg.data.enroll.samples_remaining = 0; qdev->device.notify(&msg); return; } static worker_state_t getListenerState(qemu_fingerprint_device_t* dev) { ALOGV("----------------> %s ----------------->", __FUNCTION__); worker_state_t state = STATE_IDLE; pthread_mutex_lock(&dev->lock); state = dev->listener.state; pthread_mutex_unlock(&dev->lock); return state; } /** * This a very simple event loop for the fingerprint sensor. For a given state (enroll, scan), * this would receive events from the sensor and forward them to fingerprintd using the * notify() method. * * In this simple example, we open a qemu channel (a pipe) where the developer can inject events to * exercise the API and test application code. * * The scanner should remain in the scanning state until either an error occurs or the operation * completes. * * Recoverable errors such as EINTR should be handled locally; they should not * be propagated unless there's something the user can do about it (e.g. "clean sensor"). Such * messages should go through the onAcquired() interface. * * If an unrecoverable error occurs, an acquired message (e.g. ACQUIRED_PARTIAL) should be sent, * followed by an error message (e.g. FINGERPRINT_ERROR_UNABLE_TO_PROCESS). * * Note that this event loop would typically run in TEE since it must interact with the sensor * hardware and handle raw fingerprint data and encrypted templates. It is expected that * this code monitors the TEE for resulting events, such as enrollment and authentication status. * Here we just have a very simple event loop that monitors a qemu channel for pseudo events. */ static void* listenerFunction(void* data) { ALOGD("----------------> %s ----------------->", __FUNCTION__); qemu_fingerprint_device_t* qdev = (qemu_fingerprint_device_t*)data; pthread_mutex_lock(&qdev->lock); qdev->qchanfd = qemud_channel_open(FINGERPRINT_LISTEN_SERVICE_NAME); if (qdev->qchanfd < 0) { ALOGE("listener cannot open fingerprint listener service exit"); pthread_mutex_unlock(&qdev->lock); return NULL; } qdev->listener.state = STATE_IDLE; pthread_mutex_unlock(&qdev->lock); const char* cmd = "listen"; if (qemud_channel_send(qdev->qchanfd, cmd, strlen(cmd)) < 0) { ALOGE("cannot write fingerprint 'listen' to host"); goto done_quiet; } int comm_errors = 0; struct pollfd pfd = { .fd = qdev->qchanfd, .events = POLLIN, }; while (1) { int size = 0; int fid = 0; char buffer[MAX_COMM_CHARS] = {0}; bool disconnected = false; while (1) { if (getListenerState(qdev) == STATE_EXIT) { ALOGD("Received request to exit listener thread"); goto done; } // Reset revents before poll() (just to be safe) pfd.revents = 0; // Poll qemud channel for 5 seconds // TODO: Eliminate the timeout so that polling can be interrupted // instantly. One possible solution is to follow the example of // android::Looper ($AOSP/system/core/include/utils/Looper.h and // $AOSP/system/core/libutils/Looper.cpp), which makes use of an // additional file descriptor ("wake event fd"). int nfds = poll(&pfd, 1, 5000); if (nfds < 0) { ALOGE("Could not poll qemud channel: %s", strerror(errno)); goto done; } if (!nfds) { // poll() timed out - try again continue; } // assert(nfds == 1) if (pfd.revents & POLLIN) { // Input data being available doesn't rule out a disconnection disconnected = pfd.revents & (POLLERR | POLLHUP); break; // Exit inner while loop } else { // Some event(s) other than "input data available" occurred, // i.e. POLLERR or POLLHUP, indicating a disconnection ALOGW("Lost connection to qemud channel"); goto done; } } // Shouldn't block since we were just notified of a POLLIN event if ((size = qemud_channel_recv(qdev->qchanfd, buffer, sizeof(buffer) - 1)) > 0) { buffer[size] = '\0'; if (sscanf(buffer, "on:%d", &fid) == 1) { if (fid > 0 && fid <= MAX_FID_VALUE) { switch (qdev->listener.state) { case STATE_ENROLL: send_enroll_notice(qdev, fid); break; case STATE_SCAN: send_scan_notice(qdev, fid); break; default: ALOGE("fingerprint event listener at unexpected " "state 0%x", qdev->listener.state); } } else { ALOGE("fingerprintid %d not in valid range [%d, %d] and " "will be " "ignored", fid, 1, MAX_FID_VALUE); continue; } } else if (strncmp("off", buffer, 3) == 0) { // TODO: Nothing to do here ? Looks valid ALOGD("fingerprint ID %d off", fid); } else { ALOGE("Invalid command '%s' to fingerprint listener", buffer); } if (disconnected) { ALOGW("Connection to qemud channel has been lost"); break; } } else { ALOGE("fingerprint listener receive failure"); if (comm_errors > MAX_COMM_ERRORS) break; } } done: ALOGD("Listener exit with %d receive errors", comm_errors); done_quiet: close(qdev->qchanfd); return NULL; } static int fingerprint_close(hw_device_t* device) { ALOGD("----------------> %s ----------------->", __FUNCTION__); if (device == NULL) { ALOGE("fingerprint hw device is NULL"); return -1; } qemu_fingerprint_device_t* qdev = (qemu_fingerprint_device_t*)device; pthread_mutex_lock(&qdev->lock); // Ask listener thread to exit qdev->listener.state = STATE_EXIT; pthread_mutex_unlock(&qdev->lock); pthread_join(qdev->listener.thread, NULL); pthread_mutex_destroy(&qdev->lock); free(qdev); return 0; } static int fingerprint_open(const hw_module_t* module, const char __unused *id, hw_device_t** device) { ALOGD("----------------> %s ----------------->", __FUNCTION__); if (device == NULL) { ALOGE("NULL device on open"); return -EINVAL; } qemu_fingerprint_device_t* qdev = (qemu_fingerprint_device_t*)calloc( 1, sizeof(qemu_fingerprint_device_t)); if (qdev == NULL) { ALOGE("Insufficient memory for virtual fingerprint device"); return -ENOMEM; } loadFingerprints(&qdev->listener); qdev->device.common.tag = HARDWARE_DEVICE_TAG; qdev->device.common.version = HARDWARE_MODULE_API_VERSION(2, 0); qdev->device.common.module = (struct hw_module_t*)module; qdev->device.common.close = fingerprint_close; qdev->device.pre_enroll = fingerprint_pre_enroll; qdev->device.enroll = fingerprint_enroll; qdev->device.post_enroll = fingerprint_post_enroll; qdev->device.get_authenticator_id = fingerprint_get_auth_id; qdev->device.set_active_group = fingerprint_set_active_group; qdev->device.authenticate = fingerprint_authenticate; qdev->device.cancel = fingerprint_cancel; qdev->device.enumerate = fingerprint_enumerate; qdev->device.remove = fingerprint_remove; qdev->device.set_notify = set_notify_callback; qdev->device.notify = NULL; // init and create listener thread pthread_mutex_init(&qdev->lock, NULL); if (pthread_create(&qdev->listener.thread, NULL, listenerFunction, qdev) != 0) return -1; // "Inheritance" / casting *device = &qdev->device.common; return 0; } static struct hw_module_methods_t fingerprint_module_methods = { .open = fingerprint_open, }; fingerprint_module_t HAL_MODULE_INFO_SYM = { .common = { .tag = HARDWARE_MODULE_TAG, .module_api_version = FINGERPRINT_MODULE_API_VERSION_2_0, .hal_api_version = HARDWARE_HAL_API_VERSION, .id = FINGERPRINT_HARDWARE_MODULE_ID, .name = "Emulator Fingerprint HAL", .author = "The Android Open Source Project", .methods = &fingerprint_module_methods, }, };