diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 00000000..ab7ba765 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,15 @@ +{ + "configurations": [ + { + "name": "Linux", + "includePath": ["${workspaceFolder}/**"], + "defines": [], + "compilerPath": "/usr/bin/gcc", + "cStandard": "gnu17", + "cppStandard": "gnu++14", + "intelliSenseMode": "linux-gcc-x64", + "compileCommands": "${workspaceFolder}/_build/compile_commands.json" + } + ], + "version": 4 +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..08e29ba1 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,24 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "cppdbg", + "name": "Launch examples/img-capture", + "request": "launch", + "program": "${workspaceFolder}/_build/examples/img-capture", + "args": [], + "stopAtEntry": false, + "cwd": "${fileDirname}", + "environment": [], + "MIMode": "gdb", + "avoidWindowsConsoleRedirection": true, + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ] + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..d5719014 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "files.associations": { + "*.c": "c", + "*.h": "c" + } +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 00000000..e22f0bc0 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,20 @@ +{ + "tasks": [ + { + "type": "cppbuild", + "label": "Meson: build current project", + "command": "/usr/bin/meson", + "args": ["compile"], + "options": { + "cwd": "${workspaceFolder}/_build" + }, + "problemMatcher": ["$gcc"], + "group": { + "kind": "build", + "isDefault": true + }, + "detail": "Build the current project using meson." + } + ], + "version": "2.0.0" +} diff --git a/README b/README index fbf76705..a4afb3c3 100644 --- a/README +++ b/README @@ -1,3 +1,8 @@ +This is an experimental libfprint driver implementation for Goodix drivers. + +Currently in the works: +- 27c6x5110 (80x64 resolution) + libfprint ========= diff --git a/data/autosuspend.hwdb b/data/autosuspend.hwdb index 802312d7..c0f18ee7 100644 --- a/data/autosuspend.hwdb +++ b/data/autosuspend.hwdb @@ -213,6 +213,10 @@ usb:v138Ap0018* usb:v138Ap0091* ID_AUTOSUSPEND=1 +# Supported by libfprint driver goodixtls +usb:v27c6p5110* + ID_AUTOSUSPEND=1 + # Known unsupported devices usb:v04F3p036B* usb:v04F3p0C00* @@ -263,7 +267,6 @@ usb:v1C7Ap0300* usb:v1C7Ap0570* usb:v1C7Ap0575* usb:v27C6p5042* -usb:v27C6p5110* usb:v27C6p5117* usb:v27C6p5201* usb:v27C6p521D* diff --git a/libfprint/drivers/goodixtls/goodix.c b/libfprint/drivers/goodixtls/goodix.c new file mode 100644 index 00000000..f28932db --- /dev/null +++ b/libfprint/drivers/goodixtls/goodix.c @@ -0,0 +1,972 @@ +/* + * Goodix Tls driver for libfprint + * + * Copyright (C) 2021 Alexander Meiler + * Copyright (C) 2021 Matthieu CHARETTE + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#define FP_COMPONENT "goodixtls" + +#include "drivers_api.h" +#include "goodix_proto.h" +#include "goodixtls.h" +#include "goodix.h" + +struct _FpiDeviceGoodixTls { + FpImageDevice parent; + + pthread_t tls_server_thread; + gint tls_server_sock; + SSL_CTX *tls_server_ctx; + + guint8 current_cmd; + + gpointer current_data; + guint16 current_data_len; + + FpiUsbTransfer *read_transfer; + GCancellable *read_cancellable; + + gboolean active; +}; + +G_DEFINE_TYPE(FpiDeviceGoodixTls, fpi_device_goodixtls, FP_TYPE_IMAGE_DEVICE); + +gchar *data_to_str(gpointer data, gsize data_len) { + g_autofree gchar *data_str = g_malloc(2 * data_len + 1); + + for (gsize i = 0; i < data_len; i++) + sprintf((gchar *)data_str + 2 * i, "%02x", *((guint8 *)data + i)); + + return g_steal_pointer(&data_str); +} + +/* ---- GOODIX SECTION START ---- */ + +static void goodix_receive_data(FpiSsm *ssm, FpDevice *dev, guint timeout) { + FpiUsbTransfer *transfer = fpi_usb_transfer_new(dev); + + G_DEBUG_HERE(); + + transfer->ssm = ssm; + transfer->short_is_error = FALSE; + + fpi_usb_transfer_fill_bulk(transfer, GOODIX_EP_IN, EP_IN_MAX_BUF_SIZE); + + fpi_usb_transfer_submit(transfer, timeout, NULL, goodix_receive_data_cb, + NULL); +} + +static void goodix_cmd_done(FpiSsm *ssm, FpDevice *dev, guint8 cmd) { + FpiDeviceGoodixTls *self = FPI_DEVICE_GOODIXTLS(dev); + + G_DEBUG_HERE(); + + if (self->current_cmd != cmd) { + // Received a command that is not running. This should not happen. + fp_warn("Command 0x%02x is done but the runnning command is 0x%02x", cmd, + self->current_cmd); + return; + } + + fpi_ssm_next_state(ssm); +} + +static void goodix_ack_handle(FpiSsm *ssm, FpDevice *dev, gpointer data, + gsize data_len, GDestroyNotify data_destroy) { + guint8 cmd; + gboolean has_no_config; + GError *error = NULL; + + has_no_config = goodix_decode_ack(&cmd, data, data_len, data_destroy, &error); + + if (error) goto failed; + + if (has_no_config) fp_warn("MCU has no config"); + + switch (cmd) { + case GOODIX_CMD_NOP: + fp_warn( + "Received nop ack, device might be in application production mode"); + case GOODIX_CMD_MCU_GET_IMAGE: + goto read; + case GOODIX_CMD_MCU_SWITCH_TO_FDT_DOWN: + goodix_receive_data(ssm, dev, 0); + return; + case GOODIX_CMD_MCU_SWITCH_TO_FDT_UP: + goodix_receive_data(ssm, dev, 0); + return; + case GOODIX_CMD_MCU_SWITCH_TO_FDT_MODE: + goto read; + case GOODIX_CMD_NAV_0: + goodix_cmd_done(ssm, dev, GOODIX_CMD_NAV_0); + goto read; + case GOODIX_CMD_MCU_SWITCH_TO_IDLE_MODE: + goodix_cmd_done(ssm, dev, GOODIX_CMD_MCU_SWITCH_TO_IDLE_MODE); + goto read; + case GOODIX_CMD_WRITE_SENSOR_REGISTER: + goodix_cmd_done(ssm, dev, GOODIX_CMD_WRITE_SENSOR_REGISTER); + case GOODIX_CMD_READ_SENSOR_REGISTER: + case GOODIX_CMD_UPLOAD_CONFIG_MCU: + case GOODIX_CMD_SET_POWERDOWN_SCAN_FREQUENCY: + goto read; + case GOODIX_CMD_ENABLE_CHIP: + goodix_cmd_done(ssm, dev, GOODIX_CMD_ENABLE_CHIP); + case GOODIX_CMD_RESET: + goto read; + case GOODIX_CMD_MCU_ERASE_APP: + goodix_cmd_done(ssm, dev, GOODIX_CMD_MCU_ERASE_APP); + case GOODIX_CMD_READ_OTP: + case GOODIX_CMD_FIRMWARE_VERSION: + case GOODIX_CMD_QUERY_MCU_STATE: + case GOODIX_CMD_ACK: + case GOODIX_CMD_REQUEST_TLS_CONNECTION: + goto read; + case GOODIX_CMD_TLS_SUCCESSFULLY_ESTABLISHED: + goodix_cmd_done(ssm, dev, GOODIX_CMD_TLS_SUCCESSFULLY_ESTABLISHED); + case GOODIX_CMD_PRESET_PSK_WRITE_R: + case GOODIX_CMD_PRESET_PSK_READ_R: + goto read; + default: + // Unknown command. Raising an error. + g_set_error(&error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, + "Invalid ack command: 0x%02x", cmd); + goto failed; + } + +read: + goodix_receive_data(ssm, dev, GOODIX_TIMEOUT); + return; + +failed: + fpi_ssm_mark_failed(ssm, error); + return; +} + +static void goodix_protocol_handle(FpiSsm *ssm, FpDevice *dev, gpointer data, + gsize data_len, + GDestroyNotify data_destroy) { + guint8 cmd; + gpointer payload = NULL; + guint16 payload_len, payload_ptr_len; + GError *error = NULL; + + payload_ptr_len = goodix_decode_protocol( + &cmd, &payload, &payload_len, TRUE, data, data_len, data_destroy, &error); + + if (error) goto failed; + + if (payload_ptr_len < payload_len) { + // Command is not full but packet is since we checked that before. + // This means that something when wrong. This should never happen. + // Raising an error. + // TODO implement reassembling for messages protocol beacause some device + // doesn't use essages packets. + g_set_error(&error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, + "Invalid message protocol length: %d bytes / %d bytes", + payload_ptr_len, payload_len); + goto failed; + } + + fp_dbg("Received command: cmd=0x%02x, payload=%s", cmd, + data_to_str(payload, payload_len)); + + switch (cmd) { + case GOODIX_CMD_ACK: + // Ack reply. Decoding it. + goodix_ack_handle(ssm, dev, payload, payload_ptr_len, NULL); + goto free; + + default: + // Unknown command. Raising an error. + g_set_error(&error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, + "Invalid message protocol command: 0x%02x", cmd); + goto failed; + } + + goodix_receive_data(ssm, dev, GOODIX_TIMEOUT); + goto free; + +failed: + fpi_ssm_mark_failed(ssm, error); + goto free; + +free: + g_free(payload); +} + +static void goodix_pack_handle(FpiSsm *ssm, FpDevice *dev, gpointer data, + gsize data_len, GDestroyNotify data_destroy) { + FpiDeviceGoodixTls *self = FPI_DEVICE_GOODIXTLS(dev); + guint8 flags; + gpointer payload = NULL; + guint16 payload_len, payload_ptr_len; + GError *error = NULL; + + self->current_data = + g_realloc(self->current_data, self->current_data_len + data_len); + memcpy((guint8 *)self->current_data + self->current_data_len, data, data_len); + if (data_destroy) data_destroy(data); + self->current_data_len += data_len; + + payload_ptr_len = + goodix_decode_pack(&flags, &payload, &payload_len, self->current_data, + self->current_data_len, NULL, &error); + + if (error) goto failed; + + if (payload_ptr_len < payload_len) + // Packet is not full, we still need data. Starting to read again. + goto read; + + fp_dbg("Received pack: flags=0x%02x, payload=%s", flags, + data_to_str(payload, payload_len)); + + switch (flags) { + case GOODIX_FLAGS_MSG_PROTOCOL: + // Message protocol. Decoding it. + goodix_protocol_handle(ssm, dev, payload, payload_ptr_len, NULL); + goto free; + + case GOODIX_FLAGS_TLS: + // TLS message sending it to TLS server. + // TODO + goto read; + + default: + // Unknown flags. Raising an error. + g_set_error(&error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, + "Invalid message pack flags: 0x%02x", flags); + goto failed; + } + +read: + goodix_receive_data(ssm, dev, GOODIX_TIMEOUT); + goto free; + +failed: + fpi_ssm_mark_failed(ssm, error); + goto free; + +free: + self->current_data_len = 0; + g_clear_pointer(&self->current_data, g_free); + g_free(payload); +} + +static void goodix_receive_data_cb(FpiUsbTransfer *transfer, FpDevice *dev, + gpointer user_data, GError *error) { + G_DEBUG_HERE(); + + if (error) goto failed; + + goodix_pack_handle(transfer->ssm, dev, transfer->buffer, + transfer->actual_length, NULL); + + return; + +failed: + fpi_ssm_mark_failed(transfer->ssm, error); + + // /* XXX: We used to reset the device in error cases! */ + // if (transfer->endpoint & FPI_USB_ENDPOINT_IN) { + // /* just finished receiving */ + // self->last_read = g_memdup(transfer->buffer, transfer->actual_length); + // // fp_dbg("%lu", transfer->actual_length); + // // Some devices send multiple replies, so we need to catch them + // // 0xb0 equels the ACK packet + // // Special case: Reading firmware + // if (self->cmd->cmd == read_fw.cmd) { + // if (transfer->actual_length == self->cmd->response_len && + // self->last_read[4] == 0xb0) { + // // We got ACK, now wait for the firmware version + // G_DEBUG_HERE(); + // goodix_receive_data(ssm, dev, self->cmd->response_len_2); + // } else { + // // Reading the firmware version + // self->fw_ver = g_memdup(&self->last_read[7], + // self->cmd->response_len_2); G_DEBUG_HERE(); goodix_cmd_done(ssm); + // } + // } + // // Special case: Reading PSK hash + // else if (self->cmd->cmd == read_psk.cmd) { + // if (transfer->actual_length == self->cmd->response_len && + // self->last_read[4] == 0xb0) { + // // We got ACK, now wait for the PSK + // G_DEBUG_HERE(); + // goodix_receive_data(ssm, dev, self->cmd->response_len_2); + // } else { + // /*fp_dbg("%lu", transfer->actual_length); + // gint i; + // for (i = 16; i < GOODIX_PSK_LEN+16; i++) + // { + // fp_dbg("%02x", self->last_read[i]); + // }*/ + + // // Reading the PSK + // self->sensor_psk_hash = g_memdup(&self->last_read[16], + // GOODIX_PSK_LEN); G_DEBUG_HERE(); goodix_cmd_done(ssm); + // } + // } + // // Special case: Setting MCU config + // else if (self->cmd->cmd == mcu_set_config.cmd) { + // if (transfer->actual_length == self->cmd->response_len && + // self->last_read[4] == 0xb0) { + // // We got ACK, now wait for the PSK + // G_DEBUG_HERE(); + // goodix_receive_data(ssm, dev, self->cmd->response_len_2); + // } else { + // G_DEBUG_HERE(); + // goodix_cmd_done(ssm); + // } + // } + // // Special case: Setting scan frequency + // else if (self->cmd->cmd == set_powerdown_scan_frequency.cmd) { + // if (transfer->actual_length == self->cmd->response_len && + // self->last_read[4] == 0xb0) { + // // We got ACK, now wait for the second packet + // G_DEBUG_HERE(); + // goodix_receive_data(ssm, dev, self->cmd->response_len_2); + // } else { + // G_DEBUG_HERE(); + // goodix_cmd_done(ssm); + // } + // } + // // Special case: Requesting TLS connection + // else if (self->cmd->cmd == mcu_request_tls_connection.cmd) { + // if (transfer->actual_length == self->cmd->response_len && + // self->last_read[4] == 0xb0) { + // // We got ACK, now wait for the second packet + // self->cmd_recv_counter = 1; + // G_DEBUG_HERE(); + // goodix_receive_data(ssm, dev, self->cmd->response_len_2); + // } else if (self->cmd_recv_counter == 1) { + // // Read 56 byte packet + // self->cmd_recv_counter = 2; + // G_DEBUG_HERE(); + // self->tls_msg_1 = g_memdup(&self->last_read, + // self->cmd->response_len_2); goodix_receive_data(ssm, dev, + // self->cmd->response_len_3); + // } + // /*else if(self->cmd_recv_counter == 2) + // { + // // Read first 64 byte packet + // self->cmd_recv_counter = 3; + // G_DEBUG_HERE (); + // self->tls_msg_2 = g_memdup (&self->last_read, + // self->cmd->response_len_3); goodix_receive_data (ssm, dev, + // self->cmd->response_len_4); + // }*/ + // else { + // // Read second 64 byte packet + // self->cmd_recv_counter = 0; + // G_DEBUG_HERE(); + // self->tls_msg_2 = g_memdup(&self->last_read, + // self->cmd->response_len_3); + + // fp_dbg("\n"); + + // gint i; + // for (i = 0; i < self->cmd->response_len_2; i++) { + // fp_dbg("%02x", self->tls_msg_1[i]); + // } + // fp_dbg("\n"); + // for (i = 0; i < self->cmd->response_len_3; i++) { + // fp_dbg("%02x", self->tls_msg_2[i]); + // } + + // fp_dbg("\n"); + // /*for (i = 0; i < 64; i++) + // { + // fp_dbg("%02x", self->tls_msg_3[i]); + // }*/ + // fp_dbg("\n"); + + // goodix_cmd_done(ssm); + // } + // } else { + // goodix_cmd_done(ssm); + // } + // } else { + // /* just finished sending */ + // G_DEBUG_HERE(); + // goodix_receive_data(ssm, dev, self->cmd->response_len); + // } +} + +static void goodix_send_pack(FpiSsm *ssm, FpDevice *dev, guint8 flags, + gpointer payload, guint16 payload_len, + GDestroyNotify destroy) { + FpiUsbTransfer *transfer = fpi_usb_transfer_new(dev); + GError *error = NULL; + gpointer data; + gsize data_len; + + fp_dbg("Sending pack: flags=0x%02x, payload=%s", flags, + data_to_str(payload, payload_len)); + + transfer->ssm = ssm; + transfer->short_is_error = TRUE; + + data_len = goodix_encode_pack(&data, flags, payload, payload_len, destroy); + + for (gsize i = 0; i < data_len; i += EP_OUT_MAX_BUF_SIZE) { + fpi_usb_transfer_fill_bulk_full(transfer, GOODIX_EP_OUT, (guint8 *)data + i, + EP_OUT_MAX_BUF_SIZE, g_free); + + if (!fpi_usb_transfer_submit_sync(transfer, GOODIX_TIMEOUT, &error)) + goto failed; + } + + goto free; + +failed: + fpi_ssm_mark_failed(ssm, error); + goto free; + +free: + fpi_usb_transfer_unref(transfer); +} + +static void goodix_send_protocol(FpiSsm *ssm, FpDevice *dev, guint8 cmd, + gboolean calc_checksum, gpointer payload, + guint16 payload_len, GDestroyNotify destroy) { + gpointer data; + gsize data_len; + + fp_dbg("Sending command: cmd=0x%02x, calc_checksum=%d, payload=%s", cmd, + calc_checksum, data_to_str(payload, payload_len)); + + FPI_DEVICE_GOODIXTLS(dev)->current_cmd = cmd; + + data_len = goodix_encode_protocol(&data, cmd, calc_checksum, payload, + payload_len, destroy); + + goodix_send_pack(ssm, dev, GOODIX_FLAGS_MSG_PROTOCOL, data, data_len, g_free); +} + +static void goodix_cmd_nop(FpiSsm *ssm, FpDevice *dev) { + struct _payload { + guint32 unknown; + } __attribute__((__packed__)) payload = {.unknown = 0}; + + fp_dbg("Goodix nop"); + + goodix_send_protocol(ssm, dev, GOODIX_CMD_NOP, FALSE, &payload, + sizeof(payload), NULL); +} + +static void goodix_cmd_mcu_get_image(FpiSsm *ssm, FpDevice *dev) { + struct _payload { + guint8 unused_flags; + guint8 : 8; + } __attribute__((__packed__)) payload = {.unused_flags = 0x01}; + + fp_dbg("Goodix mcu get image"); + + goodix_send_protocol(ssm, dev, GOODIX_CMD_MCU_GET_IMAGE, TRUE, &payload, + sizeof(payload), NULL); +} + +static void goodix_cmd_mcu_switch_to_fdt_down(FpiSsm *ssm, FpDevice *dev, + gpointer mode, guint16 mode_len, + GDestroyNotify destroy) { + fp_dbg("Goodix mcu switch to fdt down"); + + goodix_send_protocol(ssm, dev, GOODIX_CMD_MCU_SWITCH_TO_FDT_DOWN, TRUE, mode, + mode_len, destroy); +} + +static void goodix_cmd_mcu_switch_to_fdt_up(FpiSsm *ssm, FpDevice *dev, + gpointer mode, guint16 mode_len, + GDestroyNotify destroy) { + fp_dbg("Goodix mcu switch to fdt up"); + + goodix_send_protocol(ssm, dev, GOODIX_CMD_MCU_SWITCH_TO_FDT_UP, TRUE, mode, + mode_len, destroy); +} + +static void goodix_cmd_mcu_switch_to_fdt_mode(FpiSsm *ssm, FpDevice *dev, + gpointer mode, guint16 mode_len, + GDestroyNotify destroy) { + fp_dbg("Goodix mcu switch to fdt mode"); + + goodix_send_protocol(ssm, dev, GOODIX_CMD_MCU_SWITCH_TO_FDT_MODE, TRUE, mode, + mode_len, destroy); +} + +static void goodix_cmd_nav_0(FpiSsm *ssm, FpDevice *dev) { + struct _payload { + guint8 unused_flags; + guint8 : 8; + } __attribute__((__packed__)) payload = {.unused_flags = 0x01}; + + fp_dbg("Goodix nav 0"); + + goodix_send_protocol(ssm, dev, GOODIX_CMD_NAV_0, TRUE, &payload, + sizeof(payload), NULL); +} + +static void goodix_cmd_mcu_switch_to_idle_mode(FpiSsm *ssm, FpDevice *dev, + guint8 sleep_time) { + struct _payload { + guint8 sleep_time; + guint8 : 8; + } __attribute__((__packed__)) payload = {.sleep_time = sleep_time}; + + fp_dbg("Goodix mcu switch to idle mode"); + + goodix_send_protocol(ssm, dev, GOODIX_CMD_MCU_SWITCH_TO_IDLE_MODE, TRUE, + &payload, sizeof(payload), NULL); +} + +static void goodix_cmd_write_sensor_register(FpiSsm *ssm, FpDevice *dev, + guint16 address, guint16 value) { + // Only support one address and one value + + struct _payload { + guint8 multiples; + guint16 address; + guint16 value; + } __attribute__((__packed__)) payload = {.multiples = FALSE, + .address = GUINT16_TO_LE(address), + .value = GUINT16_TO_LE(value)}; + + fp_dbg("Goodix write sensor register"); + + goodix_send_protocol(ssm, dev, GOODIX_CMD_WRITE_SENSOR_REGISTER, TRUE, + &payload, sizeof(payload), NULL); +} + +static void goodix_cmd_read_sensor_register(FpiSsm *ssm, FpDevice *dev, + guint16 address, guint8 length) { + // Only support one address + + struct _payload { + guint8 multiples; + guint16 address; + guint8 length; + guint8 : 8; + } __attribute__((__packed__)) payload = { + .multiples = FALSE, .address = GUINT16_TO_LE(address), .length = length}; + + fp_dbg("Goodix read sensor register"); + + goodix_send_protocol(ssm, dev, GOODIX_CMD_READ_SENSOR_REGISTER, TRUE, + &payload, sizeof(payload), NULL); +} + +static void goodix_cmd_upload_config_mcu(FpiSsm *ssm, FpDevice *dev, + gpointer config, guint16 config_len, + GDestroyNotify destroy) { + fp_dbg("Goodix upload config mcu"); + + goodix_send_protocol(ssm, dev, GOODIX_CMD_UPLOAD_CONFIG_MCU, TRUE, config, + config_len, destroy); +} + +static void goodix_cmd_set_powerdown_scan_frequency( + FpiSsm *ssm, FpDevice *dev, guint16 powerdown_scan_frequency) { + struct _payload { + guint16 powerdown_scan_frequency; + } __attribute__((__packed__)) payload = { + .powerdown_scan_frequency = GUINT16_TO_LE(powerdown_scan_frequency)}; + + fp_dbg("Goodix set powerdown scan frequency"); + + goodix_send_protocol(ssm, dev, GOODIX_CMD_SET_POWERDOWN_SCAN_FREQUENCY, TRUE, + &payload, sizeof(payload), NULL); +} + +static void goodix_cmd_enable_chip(FpiSsm *ssm, FpDevice *dev, + gboolean enable) { + struct _payload { + guint8 enable; + guint8 : 8; + } __attribute__((__packed__)) payload = {.enable = enable ? TRUE : FALSE}; + + fp_dbg("Goodix enable chip"); + + goodix_send_protocol(ssm, dev, GOODIX_CMD_ENABLE_CHIP, TRUE, &payload, + sizeof(payload), NULL); +} + +static void goodix_cmd_reset(FpiSsm *ssm, FpDevice *dev, gboolean reset_sensor, + gboolean soft_reset_mcu, guint8 sleep_time) { + struct _payload { + guint8 reset_sensor : 1; + guint8 soft_reset_mcu : 1; + guint8 : 6; + guint8 sleep_time; + } __attribute__((__packed__)) + payload = {.soft_reset_mcu = soft_reset_mcu ? TRUE : FALSE, + .reset_sensor = reset_sensor ? TRUE : FALSE, + .sleep_time = sleep_time}; + + fp_dbg("Goodix reset"); + + goodix_send_protocol(ssm, dev, GOODIX_CMD_RESET, TRUE, &payload, + sizeof(payload), NULL); +} + +static void goodix_cmd_firmware_version(FpiSsm *ssm, FpDevice *dev) { + struct _payload { + guint16 : 16; + } __attribute__((__packed__)) payload = {}; + + fp_dbg("Goodix firmware version"); + + goodix_send_protocol(ssm, dev, GOODIX_CMD_FIRMWARE_VERSION, TRUE, &payload, + sizeof(payload), NULL); +} + +static void goodix_cmd_query_mcu_state(FpiSsm *ssm, FpDevice *dev) { + struct _payload { + guint8 unused_flags; + } __attribute__((__packed__)) payload = {.unused_flags = 0x55}; + + fp_dbg("Goodix query mcu state"); + + goodix_send_protocol(ssm, dev, GOODIX_CMD_QUERY_MCU_STATE, TRUE, &payload, + sizeof(payload), NULL); +} + +static void goodix_cmd_request_tls_connection(FpiSsm *ssm, FpDevice *dev) { + struct _payload { + guint16 : 16; + } __attribute__((__packed__)) payload = {}; + + fp_dbg("Goodix request tls connection"); + + goodix_send_protocol(ssm, dev, GOODIX_CMD_REQUEST_TLS_CONNECTION, TRUE, + &payload, sizeof(payload), NULL); +} + +static void goodix_cmd_tls_successfully_established(FpiSsm *ssm, + FpDevice *dev) { + struct _payload { + guint16 : 16; + } __attribute__((__packed__)) payload = {}; + + fp_dbg("Goodix tls successfully established"); + + goodix_send_protocol(ssm, dev, GOODIX_CMD_TLS_SUCCESSFULLY_ESTABLISHED, TRUE, + &payload, sizeof(payload), NULL); +} + +static void goodix_cmd_preset_psk_write_r(FpiSsm *ssm, FpDevice *dev, + guint32 address, gpointer psk, + guint32 psk_len, + GDestroyNotify destroy) { + // Only support one address, one payload and one length + + struct _payload { + guint32 address; + guint32 length; + } __attribute__((__packed__)) payload = {.address = GUINT32_TO_LE(address), + .length = GUINT32_TO_LE(psk_len)}; + gpointer payload_ptr = g_malloc(sizeof(payload) + psk_len); + + fp_dbg("Goodix preset psk write r"); + + memcpy(payload_ptr, &payload, sizeof(payload)); + memcpy((guint8 *)payload_ptr + sizeof(payload), psk, psk_len); + if (destroy) destroy(psk); + + goodix_send_protocol(ssm, dev, GOODIX_CMD_PRESET_PSK_WRITE_R, TRUE, + payload_ptr, sizeof(payload) + psk_len, g_free); +} + +static void goodix_cmd_preset_psk_read_r(FpiSsm *ssm, FpDevice *dev, + guint32 address, guint32 length) { + struct _payload { + guint32 address; + guint32 length; + } __attribute__((__packed__)) payload = {.address = GUINT32_TO_LE(address), + .length = GUINT32_TO_LE(length)}; + + fp_dbg("Goodix preset psk read r"); + + goodix_send_protocol(ssm, dev, GOODIX_CMD_PRESET_PSK_READ_R, TRUE, &payload, + sizeof(payload), NULL); +} + +/* ---- GOODIX SECTION END ---- */ + +/* ------------------------------------------------------------------------- */ + +/* ---- TLS SECTION START ---- */ + +enum tls_states { + TLS_SERVER_INIT, + TLS_SERVER_HANDSHAKE_INIT, + // TLS_MCU_REQUEST_CONNECTION, + TLS_NUM_STATES, +}; + +static void tls_run_state(FpiSsm *ssm, FpDevice *dev) { + G_DEBUG_HERE(); + + switch (fpi_ssm_get_cur_state(ssm)) { + case TLS_SERVER_INIT: + tls_server_init(ssm); + break; + + case TLS_SERVER_HANDSHAKE_INIT: + tls_server_handshake_init(); + break; + + // case TLS_MCU_REQUEST_CONNECTION: + } +} + +static void tls_complete(FpiSsm *ssm, FpDevice *dev, GError *error) { + G_DEBUG_HERE(); + + fpi_image_device_activate_complete(FP_IMAGE_DEVICE(dev), error); +} + +static void goodix_tls(FpImageDevice *dev) { + G_DEBUG_HERE(); + + fpi_ssm_start(fpi_ssm_new(FP_DEVICE(dev), tls_run_state, TLS_NUM_STATES), + tls_complete); +} + +/* ---- TLS SECTION END ---- */ + +/* ------------------------------------------------------------------------- */ + +/* ---- ACTIVE SECTION START ---- */ + +enum activate_states { + ACTIVATE_READ_AND_NOP, + ACTIVATE_ENABLE_CHIP, + ACTIVATE_NOP, + ACTIVATE_CHECK_FW_VER, + ACTIVATE_CHECK_PSK, + ACTIVATE_SET_MCU_IDLE, + ACTIVATE_SET_MCU_CONFIG, + ACTIVATE_SET_POWERDOWN_SCAN_FREQUENCY, + ACTIVATE_NUM_STATES, +}; + +static void activate_run_state(FpiSsm *ssm, FpDevice *dev) { + // FpiDeviceGoodixTls *self = FPI_DEVICE_GOODIXTLS(dev); + + G_DEBUG_HERE(); + + // gint i; + + switch (fpi_ssm_get_cur_state(ssm)) { + case ACTIVATE_READ_AND_NOP: + // Nop seems to clear the previous command buffer. But we are unable to do + // so. + goodix_receive_data(ssm, dev, GOODIX_TIMEOUT); + // DON'T ADD A BREAK HERE! + case ACTIVATE_NOP: + goodix_cmd_nop(ssm, dev); + goodix_cmd_done(ssm, dev, GOODIX_CMD_NOP); + break; + + case ACTIVATE_ENABLE_CHIP: + goodix_cmd_enable_chip(ssm, dev, TRUE); + break; + + case ACTIVATE_CHECK_FW_VER: + goodix_cmd_firmware_version(ssm, dev); + break; + + // case ACTIVATE_VERIFY_FW_VER: + // if (!strcmp(self->fw_ver, GOODIX_FIRMWARE_VERSION_SUPPORTED)) { + // // The firmware version is supported + // fpi_ssm_next_state(ssm); + // } else { + // // The firmware version is unsupported + // fpi_ssm_mark_failed(ssm, + // fpi_device_error_new_msg(FP_DEVICE_ERROR_GENERAL, + // "Unsupported + // firmware!")); + // } + // break; + + case ACTIVATE_CHECK_PSK: + goodix_cmd_preset_psk_read_r(ssm, dev, 0xbb020003, 0); + break; + + // case ACTIVATE_VERIFY_PSK: + // /*for (i = 0; i < GOODIX_PSK_LEN; i++) + // { + // fp_dbg("%02x", self->sensor_psk_hash[i]); + // } + + // fp_dbg("-----"); + + // for (i = 0; i < GOODIX_PSK_LEN; i++) + // { + // fp_dbg("%02x", zero_psk_hash[i]); + // }*/ + + // // The PSK hash matches the Zero-PSK hash + // if (!memcmp(self->sensor_psk_hash, &zero_psk_hash, + // sizeof(&self->sensor_psk_hash))) { + // // fpi_ssm_mark_completed (ssm); + // fpi_ssm_next_state(ssm); + // } else { + // // The PSK hash doesn't match + // fpi_ssm_mark_failed(ssm, + // fpi_device_error_new_msg(FP_DEVICE_ERROR_GENERAL, + // "PSK doesn't + // match!")); + // } + // break; + + case ACTIVATE_SET_MCU_IDLE: + goodix_cmd_mcu_switch_to_idle_mode(ssm, dev, 20); + break; + + case ACTIVATE_SET_MCU_CONFIG: + goodix_cmd_upload_config_mcu(ssm, dev, device_config, + sizeof(device_config), NULL); + break; + + case ACTIVATE_SET_POWERDOWN_SCAN_FREQUENCY: + goodix_cmd_set_powerdown_scan_frequency(ssm, dev, 100); + break; + } +} + +static void activate_complete(FpiSsm *ssm, FpDevice *dev, GError *error) { + FpImageDevice *image_dev = FP_IMAGE_DEVICE(dev); + + G_DEBUG_HERE(); + + fpi_image_device_activate_complete(image_dev, error); + + if (!error) goodix_tls(image_dev); +} + +/* ---- ACTIVE SECTION END ---- */ + +/* ------------------------------------------------------------------------- */ + +/* ---- DEV SECTION START ---- */ + +static void dev_init(FpImageDevice *dev) { + GError *error = NULL; + + G_DEBUG_HERE(); + + if (!g_usb_device_claim_interface(fpi_device_get_usb_device(FP_DEVICE(dev)), + GOODIX_INTERFACE, 0, &error)) { + fpi_image_device_open_complete(dev, error); + return; + } + + fpi_image_device_open_complete(dev, NULL); +} + +static void dev_deinit(FpImageDevice *dev) { + GError *error = NULL; + + G_DEBUG_HERE(); + + if (!g_usb_device_release_interface(fpi_device_get_usb_device(FP_DEVICE(dev)), + GOODIX_INTERFACE, 0, &error)) { + fpi_image_device_close_complete(dev, error); + return; + } + + fpi_image_device_close_complete(dev, NULL); +} + +static void dev_activate(FpImageDevice *dev) { + FpiDeviceGoodixTls *self = FPI_DEVICE_GOODIXTLS(dev); + + G_DEBUG_HERE(); + + self->active = TRUE; + self->current_data = NULL; + self->current_data_len = 0; + + fpi_ssm_start( + fpi_ssm_new(FP_DEVICE(dev), activate_run_state, ACTIVATE_NUM_STATES), + activate_complete); +} + +static void dev_change_state(FpImageDevice *dev, FpiImageDeviceState state) { + G_DEBUG_HERE(); + + // if (state == FPI_IMAGE_DEVICE_STATE_AWAIT_FINGER_ON) + // goodix_tls(FPI_DEVICE_GOODIX(dev)); +} + +static void dev_deactivate(FpImageDevice *dev) { + FpiDeviceGoodixTls *self = FPI_DEVICE_GOODIXTLS(dev); + + G_DEBUG_HERE(); + + if (!self->active) { + g_clear_pointer(&self->current_data, g_free); + self->current_data_len = 0; + + fpi_image_device_deactivate_complete(dev, NULL); + } else + // The device is not yet inactive, flag that we are deactivating (and need + // to signal back deactivation). Note that any running capture will be + // cancelled already if needed. + self->active = FALSE; + // TODO? +} + +/* ---- DEV SECTION END ---- */ + +/* ------------------------------------------------------------------------- */ + +/* ---- FPI SECTION START ---- */ + +static void fpi_device_goodixtls_init(FpiDeviceGoodixTls *self) {} + +static void fpi_device_goodixtls_class_init(FpiDeviceGoodixTlsClass *class) { + FpDeviceClass *dev_class = FP_DEVICE_CLASS(class); + FpImageDeviceClass *img_class = FP_IMAGE_DEVICE_CLASS(class); + + G_DEBUG_HERE(); + + dev_class->id = "goodixtls"; + dev_class->full_name = "Goodix TLS Fingerprint Sensor"; + dev_class->type = FP_DEVICE_TYPE_USB; + dev_class->id_table = id_table; + + dev_class->scan_type = FP_SCAN_TYPE_SWIPE; + + // TODO + img_class->bz3_threshold = 24; + img_class->img_width = 80; + img_class->img_height = 88; + + img_class->img_open = dev_init; + img_class->img_close = dev_deinit; + img_class->activate = dev_activate; + img_class->change_state = dev_change_state; + img_class->deactivate = dev_deactivate; + + fpi_device_class_auto_initialize_features(dev_class); + dev_class->features &= ~FP_DEVICE_FEATURE_VERIFY; +} + +/* ---- FPI SECTION END ---- */ diff --git a/libfprint/drivers/goodixtls/goodix.h b/libfprint/drivers/goodixtls/goodix.h new file mode 100644 index 00000000..e7db9750 --- /dev/null +++ b/libfprint/drivers/goodixtls/goodix.h @@ -0,0 +1,81 @@ +/* + * Goodix Tls driver for libfprint + * + * Copyright (C) 2021 Alexander Meiler + * Copyright (C) 2021 Matthieu CHARETTE + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#pragma once + +#include +#include +#include +#include +#include + +#define GOODIX_INTERFACE (0) +#define GOODIX_EP_OUT (0x1) +#define GOODIX_EP_IN (0x81) + +// 1 seconds USB timeout +#define GOODIX_TIMEOUT (1000) + +#define GOODIX_FIRMWARE_VERSION_SUPPORTED "GF_ST411SEC_APP_12109" + +#define GOODIX_PSK_LEN (32) + +guint8 zero_psk_hash[] = {0xba, 0x1a, 0x86, 0x03, 0x7c, 0x1d, 0x3c, 0x71, + 0xc3, 0xaf, 0x34, 0x49, 0x55, 0xbd, 0x69, 0xa9, + 0xa9, 0x86, 0x1d, 0x9e, 0x91, 0x1f, 0xa2, 0x49, + 0x85, 0xb6, 0x77, 0xe8, 0xdb, 0xd7, 0x2d, 0x43}; + +guint8 device_config[] = { + 0x70, 0x11, 0x60, 0x71, 0x2c, 0x9d, 0x2c, 0xc9, 0x1c, 0xe5, 0x18, 0xfd, + 0x00, 0xfd, 0x00, 0xfd, 0x03, 0xba, 0x00, 0x01, 0x80, 0xca, 0x00, 0x04, + 0x00, 0x84, 0x00, 0x15, 0xb3, 0x86, 0x00, 0x00, 0xc4, 0x88, 0x00, 0x00, + 0xba, 0x8a, 0x00, 0x00, 0xb2, 0x8c, 0x00, 0x00, 0xaa, 0x8e, 0x00, 0x00, + 0xc1, 0x90, 0x00, 0xbb, 0xbb, 0x92, 0x00, 0xb1, 0xb1, 0x94, 0x00, 0x00, + 0xa8, 0x96, 0x00, 0x00, 0xb6, 0x98, 0x00, 0x00, 0x00, 0x9a, 0x00, 0x00, + 0x00, 0xd2, 0x00, 0x00, 0x00, 0xd4, 0x00, 0x00, 0x00, 0xd6, 0x00, 0x00, + 0x00, 0xd8, 0x00, 0x00, 0x00, 0x50, 0x00, 0x01, 0x05, 0xd0, 0x00, 0x00, + 0x00, 0x70, 0x00, 0x00, 0x00, 0x72, 0x00, 0x78, 0x56, 0x74, 0x00, 0x34, + 0x12, 0x20, 0x00, 0x10, 0x40, 0x2a, 0x01, 0x02, 0x04, 0x22, 0x00, 0x01, + 0x20, 0x24, 0x00, 0x32, 0x00, 0x80, 0x00, 0x01, 0x00, 0x5c, 0x00, 0x80, + 0x00, 0x56, 0x00, 0x04, 0x20, 0x58, 0x00, 0x03, 0x02, 0x32, 0x00, 0x0c, + 0x02, 0x66, 0x00, 0x03, 0x00, 0x7c, 0x00, 0x00, 0x58, 0x82, 0x00, 0x80, + 0x15, 0x2a, 0x01, 0x82, 0x03, 0x22, 0x00, 0x01, 0x20, 0x24, 0x00, 0x14, + 0x00, 0x80, 0x00, 0x01, 0x00, 0x5c, 0x00, 0x00, 0x01, 0x56, 0x00, 0x04, + 0x20, 0x58, 0x00, 0x03, 0x02, 0x32, 0x00, 0x0c, 0x02, 0x66, 0x00, 0x03, + 0x00, 0x7c, 0x00, 0x00, 0x58, 0x82, 0x00, 0x80, 0x1f, 0x2a, 0x01, 0x08, + 0x00, 0x5c, 0x00, 0x80, 0x00, 0x54, 0x00, 0x10, 0x01, 0x62, 0x00, 0x04, + 0x03, 0x64, 0x00, 0x19, 0x00, 0x66, 0x00, 0x03, 0x00, 0x7c, 0x00, 0x01, + 0x58, 0x2a, 0x01, 0x08, 0x00, 0x5c, 0x00, 0x00, 0x01, 0x52, 0x00, 0x08, + 0x00, 0x54, 0x00, 0x00, 0x01, 0x66, 0x00, 0x03, 0x00, 0x7c, 0x00, 0x01, + 0x58, 0x00, 0x89, 0x2e}; + +G_DECLARE_FINAL_TYPE(FpiDeviceGoodixTls, fpi_device_goodixtls, FPI, + DEVICE_GOODIXTLS, FpImageDevice); + +static const FpIdEntry id_table[] = { + {.vid = 0x27c6, .pid = 0x5110}, + {.vid = 0, .pid = 0, .driver_data = 0}, +}; + +static void goodix_receive_data_cb(FpiUsbTransfer *transfer, FpDevice *dev, + gpointer user_data, GError *error); + +gchar *data_to_str(gpointer data, gsize data_len); diff --git a/libfprint/drivers/goodixtls/goodix_proto.c b/libfprint/drivers/goodixtls/goodix_proto.c new file mode 100644 index 00000000..e9f70b37 --- /dev/null +++ b/libfprint/drivers/goodixtls/goodix_proto.c @@ -0,0 +1,191 @@ +/* + * Goodix Tls driver for libfprint + * + * Copyright (C) 2021 Alexander Meiler + * Copyright (C) 2021 Matthieu CHARETTE + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "goodix_proto.h" + +guint8 goodix_calc_checksum(gpointer data, guint16 data_len) { + guint8 checksum = 0; + + for (guint16 i = 0; i < data_len; i++) checksum += *((guint8 *)data + i); + + return checksum; +} + +gsize goodix_encode_pack(gpointer *data, guint8 flags, gpointer payload, + guint16 payload_len, GDestroyNotify payload_destroy) { + struct _pack { + guint8 flags; + guint16 length; + guint8 checksum; + } __attribute__((__packed__)) + pack = {.flags = flags, + .length = GUINT16_TO_LE(payload_len), + .checksum = goodix_calc_checksum(&flags, sizeof(flags)) + + goodix_calc_checksum(&payload_len, sizeof(payload_len))}; + gsize data_ptr_len = sizeof(pack) + payload_len; + + if (data_ptr_len % EP_OUT_MAX_BUF_SIZE) + data_ptr_len += EP_OUT_MAX_BUF_SIZE - data_ptr_len % EP_OUT_MAX_BUF_SIZE; + + *data = g_malloc0(data_ptr_len); + + memcpy(*data, &pack, sizeof(pack)); + memcpy((guint8 *)*data + sizeof(pack), payload, payload_len); + if (payload_destroy) payload_destroy(payload); + + return data_ptr_len; +} + +gsize goodix_encode_protocol(gpointer *data, guint8 cmd, gboolean calc_checksum, + gpointer payload, guint16 payload_len, + GDestroyNotify payload_destroy) { + struct _protocol { + guint8 cmd; + guint16 length; + } __attribute__((__packed__)) + protocol = {.cmd = cmd, .length = GUINT16_TO_LE(payload_len) + 1}; + gsize payload_ptr_len = sizeof(protocol) + payload_len + 1; + + *data = g_malloc(payload_ptr_len); + + memcpy(*data, &protocol, sizeof(protocol)); + memcpy((guint8 *)*data + sizeof(protocol), payload, payload_len); + if (payload_destroy) payload_destroy(payload); + + if (calc_checksum) + *((guint8 *)*data + payload_ptr_len - 1) = + 0xaa - goodix_calc_checksum(*data, payload_ptr_len - 1); + else + *((guint8 *)*data + payload_ptr_len - 1) = 0x88; + + return payload_ptr_len; +} + +guint16 goodix_decode_pack(guint8 *flags, gpointer *payload, + guint16 *payload_len, gpointer data, gsize data_len, + GDestroyNotify data_destroy, GError **error) { + struct _pack { + guint8 flags; + guint16 length; + guint8 checksum; + } __attribute__((__packed__)) pack; + guint16 payload_ptr_len = data_len - sizeof(pack); + + if (data_len < sizeof(pack)) { + g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, + "Invalid message pack length"); + return 0; + } + + memcpy(&pack, data, sizeof(pack)); + pack.length = GUINT16_FROM_LE(pack.length); + + if (payload_ptr_len >= pack.length) payload_ptr_len = pack.length; + + if (goodix_calc_checksum(data, sizeof(pack) - 1) != pack.checksum) { + g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, + "Invalid message pack checksum"); + return 0; + } + + *payload = g_memdup((guint8 *)data + sizeof(pack), payload_ptr_len); + if (data_destroy) data_destroy(data); + + *flags = pack.flags; + *payload_len = pack.length; + + return payload_ptr_len; +} + +guint16 goodix_decode_protocol(guint8 *cmd, gpointer *payload, + guint16 *payload_len, gboolean calc_checksum, + gpointer data, gsize data_len, + GDestroyNotify data_destroy, GError **error) { + struct _protocol { + guint8 cmd; + guint16 length; + } __attribute__((__packed__)) protocol; + guint16 payload_ptr_len = data_len - sizeof(protocol) - 1; + + if (data_len - 1 < sizeof(protocol)) { + g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, + "Invalid message protocol length"); + return 0; + } + + memcpy(&protocol, data, sizeof(protocol)); + protocol.length = GUINT16_FROM_LE(protocol.length) - 1; + + if (payload_ptr_len >= protocol.length) { + payload_ptr_len = protocol.length; + + if (calc_checksum) { + if (*((guint8 *)data + sizeof(protocol) + protocol.length) != + 0xaa - + goodix_calc_checksum(data, sizeof(protocol) + protocol.length)) { + g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, + "Invalid message protocol checksum"); + return 0; + } + } else if (*((guint8 *)data + sizeof(protocol) + protocol.length) != 0x88) { + g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, + "Invalid message protocol checksum"); + return 0; + } + } + + *payload = g_memdup((guint8 *)data + sizeof(protocol), payload_ptr_len); + if (data_destroy) data_destroy(data); + + *cmd = protocol.cmd; + *payload_len = protocol.length; + + return payload_ptr_len; +} + +gboolean goodix_decode_ack(guint8 *cmd, gpointer data, guint16 data_len, + GDestroyNotify data_destroy, GError **error) { + struct _ack { + guint8 cmd; + guint8 always_true : 1; + guint8 has_no_config : 1; + guint8 : 6; + } __attribute__((__packed__)) ack; + + if (data_len != sizeof(ack)) { + g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, + "Invalid ack length"); + return 0; + } + + memcpy(&ack, data, sizeof(ack)); + if (data_destroy) data_destroy(data); + + if (!ack.always_true) { + g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, + "Invalid ack always true value"); + return 0; + } + + *cmd = ack.cmd; + + return ack.has_no_config; +} diff --git a/libfprint/drivers/goodixtls/goodix_proto.h b/libfprint/drivers/goodixtls/goodix_proto.h new file mode 100644 index 00000000..72a07d8b --- /dev/null +++ b/libfprint/drivers/goodixtls/goodix_proto.h @@ -0,0 +1,75 @@ +/* + * Goodix Tls driver for libfprint + * + * Copyright (C) 2021 Alexander Meiler + * Copyright (C) 2021 Matthieu CHARETTE + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#pragma once + +#include "drivers_api.h" +#include + +#define EP_IN_MAX_BUF_SIZE (0x2000) +#define EP_OUT_MAX_BUF_SIZE (0x40) + +#define GOODIX_FLAGS_MSG_PROTOCOL (0xa0) +#define GOODIX_FLAGS_TLS (0xb0) + +#define GOODIX_CMD_NOP (0x00) +#define GOODIX_CMD_MCU_GET_IMAGE (0x20) +#define GOODIX_CMD_MCU_SWITCH_TO_FDT_DOWN (0x32) +#define GOODIX_CMD_MCU_SWITCH_TO_FDT_UP (0x34) +#define GOODIX_CMD_MCU_SWITCH_TO_FDT_MODE (0x36) +#define GOODIX_CMD_NAV_0 (0x50) +#define GOODIX_CMD_MCU_SWITCH_TO_IDLE_MODE (0x70) +#define GOODIX_CMD_WRITE_SENSOR_REGISTER (0x80) +#define GOODIX_CMD_READ_SENSOR_REGISTER (0x82) +#define GOODIX_CMD_UPLOAD_CONFIG_MCU (0x90) +#define GOODIX_CMD_SET_POWERDOWN_SCAN_FREQUENCY (0x94) +#define GOODIX_CMD_ENABLE_CHIP (0x96) +#define GOODIX_CMD_RESET (0xa2) +#define GOODIX_CMD_MCU_ERASE_APP (0xa4) +#define GOODIX_CMD_READ_OTP (0xa6) +#define GOODIX_CMD_FIRMWARE_VERSION (0xa8) +#define GOODIX_CMD_QUERY_MCU_STATE (0xae) +#define GOODIX_CMD_ACK (0xb0) +#define GOODIX_CMD_REQUEST_TLS_CONNECTION (0xd0) +#define GOODIX_CMD_TLS_SUCCESSFULLY_ESTABLISHED (0xd4) +#define GOODIX_CMD_PRESET_PSK_WRITE_R (0xe0) +#define GOODIX_CMD_PRESET_PSK_READ_R (0xe4) + +guint8 goodix_calc_checksum(gpointer data, guint16 data_len); + +gsize goodix_encode_pack(gpointer *data, guint8 flags, gpointer payload, + guint16 payload_len, GDestroyNotify payload_destroy); + +gsize goodix_encode_protocol(gpointer *data, guint8 cmd, gboolean calc_checksum, + gpointer payload, guint16 payload_len, + GDestroyNotify payload_destroy); + +guint16 goodix_decode_pack(guint8 *flags, gpointer *payload, + guint16 *payload_len, gpointer data, gsize data_len, + GDestroyNotify data_destroy, GError **error); + +guint16 goodix_decode_protocol(guint8 *cmd, gpointer *payload, + guint16 *payload_len, gboolean calc_checksum, + gpointer data, gsize data_len, + GDestroyNotify data_destroy, GError **error); + +gboolean goodix_decode_ack(guint8 *cmd, gpointer data, guint16 data_len, + GDestroyNotify data_destroy, GError **error); diff --git a/libfprint/drivers/goodixtls/goodixtls.c b/libfprint/drivers/goodixtls/goodixtls.c new file mode 100644 index 00000000..85df5a3f --- /dev/null +++ b/libfprint/drivers/goodixtls/goodixtls.c @@ -0,0 +1,221 @@ +/* + * Goodix Tls driver for libfprint + * + * Copyright (C) 2021 Alexander Meiler + * Copyright (C) 2021 Matthieu CHARETTE + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "goodixtls.h" + +int sock; +FpiSsm *fpi_ssm; +pthread_t server; +SSL_CTX *ctx; + +static unsigned int tls_server_psk_server_callback(SSL *ssl, + const char *identity, + unsigned char *psk, + unsigned int max_psk_len) { + long key_len = 0; + unsigned char *key; + + key = OPENSSL_hexstr2buf(psk_key, &key_len); + if (key == NULL) { + fp_dbg("OpenSSL cannot convert provided PSK"); + // fpi_ssm_mark_failed (ssm, fpi_device_error_new_msg + // (FP_DEVICE_ERROR_GENERAL, "OpenSSL cannot convert provided PSK")); + return 0; + } + if (key_len > (int)max_psk_len) { + fp_dbg("Provided PSK is too long for OpenSSL"); + // fpi_ssm_mark_failed (ssm, fpi_device_error_new_msg + // (FP_DEVICE_ERROR_GENERAL, "Provided PSK is too long for OpenSSL")); + OPENSSL_free(key); + return 0; + } + + memcpy(psk, key, key_len); + OPENSSL_free(key); + + return key_len; +} + +int tls_server_create_socket(int port) { + int s; + struct sockaddr_in addr; + + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = htonl(INADDR_ANY); + + s = socket(AF_INET, SOCK_STREAM, 0); + if (s < 0) { + fp_dbg("Unable to create TLS server socket"); + fpi_ssm_mark_failed(fpi_ssm, fpi_device_error_new_msg( + FP_DEVICE_ERROR_GENERAL, + "Unable to create TLS server socket")); + return -1; + } + + if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + fp_dbg("Unable to bind to TLS server socket"); + fpi_ssm_mark_failed(fpi_ssm, fpi_device_error_new_msg( + FP_DEVICE_ERROR_GENERAL, + "Unable to bind to TLS server socket")); + return -1; + } + + if (listen(s, 1) < 0) { + fp_dbg("Unable to listen to TLS server socket"); + fpi_ssm_mark_failed(fpi_ssm, fpi_device_error_new_msg( + FP_DEVICE_ERROR_GENERAL, + "Unable to listen to TLS server socket")); + return -1; + } + + return s; +} + +// EVP_cleanup is deprecated +/*void TLS_server_cleanup() +{ + EVP_cleanup(); +}*/ + +SSL_CTX *tls_server_create_ctx(void) { + const SSL_METHOD *method; + + method = SSLv23_server_method(); + + ctx = SSL_CTX_new(method); + if (!ctx) { + return NULL; + } + + return ctx; +} + +void tls_server_config_ctx(void) { + SSL_CTX_set_ecdh_auto(ctx, 1); + SSL_CTX_set_dh_auto(ctx, 1); + SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION); + SSL_CTX_set_max_proto_version(ctx, TLS1_2_VERSION); + SSL_CTX_set_cipher_list(ctx, "ALL"); + + SSL_CTX_set_psk_server_callback(ctx, tls_server_psk_server_callback); +} + +void *tls_server_loop(void *arg) { + /* Handle connections */ + while (1) { + struct sockaddr_in addr; + guint len = sizeof(addr); + SSL *ssl; + const char reply[] = "Hello Goodix\n"; + + int client = accept(sock, (struct sockaddr *)&addr, &len); + if (client < 0) { + printf("%s\n", strerror(errno)); + fp_dbg("TLS server unable to accept request"); + kill(getpid(), SIGKILL); + // exit(EXIT_FAILURE); + } + + ssl = SSL_new(ctx); + SSL_set_fd(ssl, client); + + if (SSL_accept(ssl) <= 0) { + ERR_print_errors_fp(stderr); + } else { + SSL_write(ssl, reply, strlen(reply)); + } + SSL_shutdown(ssl); + SSL_free(ssl); + close(client); + + kill(getpid(), SIGKILL); + } +} + +void tls_server_stop(void) { + close(sock); + SSL_CTX_free(ctx); +} + +void *tls_server_handshake_loop(void *arg) { + struct sockaddr_in addr; + guint len = sizeof(addr); + SSL *ssl; + + int client = accept(sock, (struct sockaddr *)&addr, &len); + if (client < 0) { + printf("%s\n", strerror(errno)); + fp_dbg("TLS server unable to accept socket request"); + } else { + ssl = SSL_new(ctx); + SSL_set_fd(ssl, client); + + if (SSL_accept(ssl) <= 0) { + printf("%s\n", strerror(errno)); + fp_dbg("TLS server unable to accept handshake request"); + } + } + return 0; +} + +void tls_server_handshake_init(void) { + int err = pthread_create(&server, NULL, &tls_server_handshake_loop, NULL); + if (err != 0) { + fp_dbg("Unable to create TLS server thread"); + fpi_ssm_mark_failed(fpi_ssm, fpi_device_error_new_msg( + FP_DEVICE_ERROR_GENERAL, + "Unable to create TLS server thread")); + } else { + fp_dbg("TLS server thread created"); + fpi_ssm_next_state(fpi_ssm); + } +} + +void tls_server_init(FpiSsm *ssm) { + fpi_ssm = ssm; + + SSL_load_error_strings(); + OpenSSL_add_ssl_algorithms(); + + ctx = tls_server_create_ctx(); + + if (ctx == NULL) { + fp_dbg("Unable to create TLS server context"); + fpi_ssm_mark_failed( + ssm, fpi_device_error_new_msg(FP_DEVICE_ERROR_GENERAL, + "Unable to create TLS server context")); + return; + } + + tls_server_config_ctx(); + + sock = tls_server_create_socket(GOODIX_TLS_SERVER_PORT); + if (sock == -1) { + fp_dbg("Unable to create TLS server socket"); + fpi_ssm_mark_failed( + ssm, fpi_device_error_new_msg(FP_DEVICE_ERROR_GENERAL, + "Unable to create TLS server context")); + return; + } + + fpi_ssm_next_state(ssm); +} diff --git a/libfprint/drivers/goodixtls/goodixtls.h b/libfprint/drivers/goodixtls/goodixtls.h new file mode 100644 index 00000000..74e43f97 --- /dev/null +++ b/libfprint/drivers/goodixtls/goodixtls.h @@ -0,0 +1,58 @@ +/* + * Goodix Tls driver for libfprint + * + * Copyright (C) 2021 Alexander Meiler + * Copyright (C) 2021 Matthieu CHARETTE + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "drivers_api.h" + +static const char *psk_key = + "ba1a86037c1d3c71c3af344955bd69a9a9861d9e911fa24985b677e8dbd72d43"; + +#define GOODIX_TLS_SERVER_PORT 4433 + +//#define TLS_PSK_WITH_AES_128_GCM_SHA256 ((const unsigned char *)"\x00\xa8") + +SSL_CTX *tls_server_create_ctx(void); + +int tls_server_create_socket(int port); + +void tls_server_config_ctx(void); + +__attribute__((__noreturn__)) void *tls_server_loop(void *arg); + +void tls_server_stop(void); + +void *tls_server_handshake_loop(void *arg); + +void tls_server_handshake_init(void); + +void tls_server_init(FpiSsm *ssm); diff --git a/libfprint/fprint-list-udev-hwdb.c b/libfprint/fprint-list-udev-hwdb.c index 42df9368..184ec733 100644 --- a/libfprint/fprint-list-udev-hwdb.c +++ b/libfprint/fprint-list-udev-hwdb.c @@ -78,7 +78,6 @@ static const FpIdEntry whitelist_id_table[] = { { .vid = 0x1c7a, .pid = 0x0570 }, { .vid = 0x1c7a, .pid = 0x0575 }, { .vid = 0x27c6, .pid = 0x5042 }, - { .vid = 0x27c6, .pid = 0x5110 }, { .vid = 0x27c6, .pid = 0x5117 }, { .vid = 0x27c6, .pid = 0x5201 }, { .vid = 0x27c6, .pid = 0x521d }, diff --git a/libfprint/meson.build b/libfprint/meson.build index fa46f7de..395b93c1 100644 --- a/libfprint/meson.build +++ b/libfprint/meson.build @@ -189,6 +189,9 @@ foreach driver: drivers 'drivers/nb1010.c', ] endif + if driver == 'goodixtls' + drivers_sources += ['drivers/goodixtls/goodix.c', 'drivers/goodixtls/goodixtls.c', 'drivers/goodixtls/goodix_proto.c'] + endif endforeach if aeslib @@ -238,6 +241,8 @@ deps = [ imaging_dep, mathlib_dep, nss_dep, + openssl_dep, + threads_dep, ] # These are empty and only exist so that the include directories are created diff --git a/meson.build b/meson.build index 6ffb79c3..90f2a0f1 100644 --- a/meson.build +++ b/meson.build @@ -83,6 +83,8 @@ gio_dep = dependency('gio-unix-2.0', version: '>=' + glib_min_version) gobject_dep = dependency('gobject-2.0', version: '>=' + glib_min_version) gusb_dep = dependency('gusb', version: '>= 0.2.0') mathlib_dep = cc.find_library('m', required: false) +openssl_dep = dependency('', required: false) +threads_dep = dependency('', required: false) # The following dependencies are only used for tests cairo_dep = dependency('cairo', required: false) @@ -125,7 +127,8 @@ default_drivers = [ 'upeksonly', 'upekts', 'goodixmoc', - 'nb1010' + 'nb1010', + 'goodixtls' ] # FIXME: All the drivers should be fixed by adjusting the byte order. @@ -183,6 +186,16 @@ foreach driver: drivers endif libfprint_conf.set10('HAVE_PIXMAN', true) + endif + if driver == 'goodixtls' + openssl_dep = dependency('openssl', required: false) + if not openssl_dep.found() + error('OpenSSL is required for the Goodix TLS driver') + endif + threads_dep = dependency('threads', required: false) + if not threads_dep.found() + error('Threads is required for the Goodix TLS driver') + endif endif if udev_drivers.contains(driver) install_udev_rules = true