From e8b53e63d086d9efbb90fee77640d21211da80e3 Mon Sep 17 00:00:00 2001 From: wangyucheng Date: Fri, 21 Apr 2023 10:33:27 +0800 Subject: [PATCH 1/3] fix(cmake): qt5 cmake command compatible MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 兼容qt5,cmake命令的兼容修改 --- ...x-cmake-qt5-cmake-command-compatible.patch | 54 +++++++++++++++++++ kiran-authentication-devices.spec | 6 ++- 2 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 0002-fix-cmake-qt5-cmake-command-compatible.patch diff --git a/0002-fix-cmake-qt5-cmake-command-compatible.patch b/0002-fix-cmake-qt5-cmake-command-compatible.patch new file mode 100644 index 0000000..16d0c36 --- /dev/null +++ b/0002-fix-cmake-qt5-cmake-command-compatible.patch @@ -0,0 +1,54 @@ +From ef9a87e0379051c1c89b7d8b7955fba8c1d70f28 Mon Sep 17 00:00:00 2001 +From: wangyucheng +Date: Fri, 21 Apr 2023 10:25:10 +0800 +Subject: [PATCH] fix(cmake): qt5 cmake command compatible +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +- 兼容qt5 cmake命令的兼容修改 +--- + src/CMakeLists.txt | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt +index cac6cb4..041f0dc 100644 +--- a/src/CMakeLists.txt ++++ b/src/CMakeLists.txt +@@ -18,7 +18,7 @@ file(GLOB_RECURSE SRC_CPP_FILES ./*.cpp) + + ADD_DEFINITIONS(-DQT_NO_KEYWORDS) + +-qt_add_dbus_adaptor( ++qt5_add_dbus_adaptor( + AUTH_DEVICE_MANAGER_ADAPTOR_SRCS + ${CMAKE_SOURCE_DIR}/data/com.kylinsec.Kiran.AuthDevice.xml + ${CMAKE_SOURCE_DIR}/src/auth-device-manager.h +@@ -26,7 +26,7 @@ qt_add_dbus_adaptor( + auth_device_manager_adaptor + AuthDeviceManagerAdaptor) + +- qt_add_dbus_adaptor( ++ qt5_add_dbus_adaptor( + AUTH_DEVICE_ADAPTOR_SRCS + ${CMAKE_SOURCE_DIR}/data/com.kylinsec.Kiran.AuthDevice.Device.xml + ${CMAKE_SOURCE_DIR}/src/device/auth-device.h +@@ -41,7 +41,7 @@ set(CMAKE_INSTALL_RPATH ${DEVICE_SDK}/finger-vein/sd:${DEVICE_SDK}/fingerprint/z + + + set(TS_FILES "${PROJECT_SOURCE_DIR}/translations/${PROJECT_NAME}.zh_CN.ts") +-qt_create_translation(QM_FILES ++qt5_create_translation(QM_FILES + ${CMAKE_CURRENT_SOURCE_DIR} + ${TS_FILES} + ) +@@ -80,4 +80,4 @@ install(TARGETS ${PROJECT_NAME} + + set(TRANSLATION_INSTALL_DIR ${CMAKE_INSTALL_FULL_DATADIR}/${PROJECT_NAME}/translations) + configure_file(config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h) +-install(FILES ${QM_FILES} DESTINATION ${TRANSLATION_INSTALL_DIR}) +\ No newline at end of file ++install(FILES ${QM_FILES} DESTINATION ${TRANSLATION_INSTALL_DIR}) +-- +2.33.0 + diff --git a/kiran-authentication-devices.spec b/kiran-authentication-devices.spec index 111a758..81ebf0d 100644 --- a/kiran-authentication-devices.spec +++ b/kiran-authentication-devices.spec @@ -1,13 +1,14 @@ Name: kiran-authentication-devices Version: 2.5.0 -Release: 3 +Release: 4 Summary: Kiran Authentication Devices License: MulanPSL-2.0 Source0: %{name}-%{version}.tar.gz Patch0001: 0001-feature-ukey-The-UKey-device-is-adapted.patch +Patch0002: 0002-fix-cmake-qt5-cmake-command-compatible.patch BuildRequires: cmake BuildRequires: gcc-c++ @@ -64,6 +65,9 @@ systemctl enable kiran-authentication-devices.service rm -rf ${buildroot} %changelog +* Fri Apr 21 2023 wangyucheng - 2.5.0-4 +- KYOS-F: qt5 cmake command compatible. + * Thu Apr 20 2023 luoqing - 2.5.0-3 - KYOS-F: Add libusb dependency. From fe0b860002579ace79e8001ca99e5e4111319e09 Mon Sep 17 00:00:00 2001 From: wangyucheng Date: Fri, 21 Apr 2023 14:01:36 +0800 Subject: [PATCH 2/3] fix ld error: undefined reference to symbol 'dlclose@@GLIBC_2.2.5' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复在低于2.34版本的glibc环境中出现的链接错误 --- ...ld-error-undefined-reference-to-symb.patch | 29 +++++++++++++++++++ kiran-authentication-devices.spec | 6 +++- 2 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 0003-fix-compile-fix-ld-error-undefined-reference-to-symb.patch diff --git a/0003-fix-compile-fix-ld-error-undefined-reference-to-symb.patch b/0003-fix-compile-fix-ld-error-undefined-reference-to-symb.patch new file mode 100644 index 0000000..f556277 --- /dev/null +++ b/0003-fix-compile-fix-ld-error-undefined-reference-to-symb.patch @@ -0,0 +1,29 @@ +From 1e33c89f9aabdc5362b8fbd1720cf45d117d888c Mon Sep 17 00:00:00 2001 +From: wangyucheng +Date: Fri, 21 Apr 2023 13:51:30 +0800 +Subject: [PATCH] fix(compile): fix ld error: undefined reference to symbol + 'dlclose@@GLIBC_2.2.5' +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +- 修复在低于2.34版本的glibc环境中出现的链接错误(从glibc2.34开始,将dl的实现放在了glibc中,所以在glibc2.34的环境中不需要链接dl,但是在低于2.34的环境还是需要链接) +--- + src/CMakeLists.txt | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt +index 041f0dc..c6c2001 100644 +--- a/src/CMakeLists.txt ++++ b/src/CMakeLists.txt +@@ -69,6 +69,7 @@ target_link_libraries(${PROJECT_NAME} + Qt5::DBus + Qt5::Sql + Qt5::Concurrent ++ ${CMAKE_DL_LIBS} + ) + + target_link_directories(${PROJECT_NAME} PRIVATE +-- +2.33.0 + diff --git a/kiran-authentication-devices.spec b/kiran-authentication-devices.spec index 81ebf0d..ac3a55a 100644 --- a/kiran-authentication-devices.spec +++ b/kiran-authentication-devices.spec @@ -1,7 +1,7 @@ Name: kiran-authentication-devices Version: 2.5.0 -Release: 4 +Release: 5 Summary: Kiran Authentication Devices License: MulanPSL-2.0 @@ -9,6 +9,7 @@ Source0: %{name}-%{version}.tar.gz Patch0001: 0001-feature-ukey-The-UKey-device-is-adapted.patch Patch0002: 0002-fix-cmake-qt5-cmake-command-compatible.patch +Patch0003: 0003-fix-compile-fix-ld-error-undefined-reference-to-symb.patch BuildRequires: cmake BuildRequires: gcc-c++ @@ -65,6 +66,9 @@ systemctl enable kiran-authentication-devices.service rm -rf ${buildroot} %changelog +* Fri Apr 21 2023 wangyucheng - 2.5.0-5 +- KYOS-F: fix ld error: undefined reference to symbol 'dlclose@@GLIBC_2.2.5'. + * Fri Apr 21 2023 wangyucheng - 2.5.0-4 - KYOS-F: qt5 cmake command compatible. From 2895d8ad0b86e26fb82c6f92a42cf58994063542 Mon Sep 17 00:00:00 2001 From: luoqing Date: Wed, 24 May 2023 14:46:38 +0800 Subject: [PATCH 3/3] feature(*):add Ukey management tool;added iristar device , supporting iris and facial enroll and identify;fix some defects. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增Ukey管理工具 新增iristar设备,支持虹膜、人脸的录入和验证 修复一些缺陷 --- ...ture-ukey-The-UKey-device-is-adapted.patch | 3101 ----------------- ...x-cmake-qt5-cmake-command-compatible.patch | 54 - ...ld-error-undefined-reference-to-symb.patch | 29 - kiran-authentication-devices-2.5.0.tar.gz | Bin 36813 -> 0 bytes kiran-authentication-devices-2.5.1.tar.gz | Bin 0 -> 65592 bytes kiran-authentication-devices.spec | 14 +- 6 files changed, 8 insertions(+), 3190 deletions(-) delete mode 100644 0001-feature-ukey-The-UKey-device-is-adapted.patch delete mode 100644 0002-fix-cmake-qt5-cmake-command-compatible.patch delete mode 100644 0003-fix-compile-fix-ld-error-undefined-reference-to-symb.patch delete mode 100644 kiran-authentication-devices-2.5.0.tar.gz create mode 100644 kiran-authentication-devices-2.5.1.tar.gz diff --git a/0001-feature-ukey-The-UKey-device-is-adapted.patch b/0001-feature-ukey-The-UKey-device-is-adapted.patch deleted file mode 100644 index 5388aad..0000000 --- a/0001-feature-ukey-The-UKey-device-is-adapted.patch +++ /dev/null @@ -1,3101 +0,0 @@ -From 919d46a3371e4770ca13ac25e5ce3fd996710d9e Mon Sep 17 00:00:00 2001 -From: luoqing -Date: Thu, 13 Apr 2023 15:41:16 +0800 -Subject: [PATCH] feature(ukey):The UKey device is adapted -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -- 完成UKey设备适配 ---- - data/drivers.conf | 6 +- - include/auth-enum.h | 4 +- - include/kiran-auth-device-i.h | 7 + - include/third-party-device.h | 5 +- - include/ukey-skf.h | 325 +++++++++++ - src/CMakeLists.txt | 9 +- - src/auth-device-manager.cpp | 45 +- - src/auth-device-manager.h | 24 +- - src/context/context-factory.cpp | 25 +- - src/context/context-factory.h | 11 +- - src/context/finger-vein/fv-sd-context.cpp | 1 + - src/context/ukey/ukey-ft-context.cpp | 57 ++ - src/context/ukey/ukey-ft-context.h | 27 + - src/device/auth-device.cpp | 228 ++++---- - src/device/auth-device.h | 65 +-- - src/device/bio-device.cpp | 104 ++++ - src/device/bio-device.h | 47 ++ - src/device/finger-vein/fv-sd-device.cpp | 28 +- - src/device/finger-vein/fv-sd-device.h | 10 +- - src/device/fingerprint/fp-device.cpp | 2 +- - src/device/fingerprint/fp-device.h | 4 +- - src/device/fingerprint/fp-zk-device.cpp | 22 +- - src/device/fingerprint/fp-zk-device.h | 7 +- - src/device/ukey/ukey-ft-device.cpp | 400 ++++++++++++++ - src/device/ukey/ukey-ft-device.h | 68 +++ - src/driver/ukey/ukey-skf-driver.cpp | 516 ++++++++++++++++++ - src/driver/ukey/ukey-skf-driver.h | 62 +++ - .../kiran-authentication-devices.zh_CN.ts | 82 ++- - 28 files changed, 1916 insertions(+), 275 deletions(-) - create mode 100644 include/ukey-skf.h - create mode 100644 src/context/ukey/ukey-ft-context.cpp - create mode 100644 src/context/ukey/ukey-ft-context.h - create mode 100644 src/device/bio-device.cpp - create mode 100644 src/device/bio-device.h - create mode 100644 src/device/ukey/ukey-ft-device.cpp - create mode 100644 src/device/ukey/ukey-ft-device.h - create mode 100644 src/driver/ukey/ukey-skf-driver.cpp - create mode 100644 src/driver/ukey/ukey-skf-driver.h - -diff --git a/data/drivers.conf b/data/drivers.conf -index e2be3f1..ef3f9f8 100644 ---- a/data/drivers.conf -+++ b/data/drivers.conf -@@ -8,4 +8,8 @@ Type=0 - - [sdfv] - Enable=true --Type=2 -\ No newline at end of file -+Type=2 -+ -+[es_3000gm] -+Enable=true -+Type=5 -\ No newline at end of file -diff --git a/include/auth-enum.h b/include/auth-enum.h -index 19f98d1..a29e9b5 100644 ---- a/include/auth-enum.h -+++ b/include/auth-enum.h -@@ -93,7 +93,9 @@ enum IdentifyProcess - // 匹配 - IDENTIFY_PROCESS_MACTCH, - // 不匹配 -- IDENTIFY_PROCESS_NO_MATCH -+ IDENTIFY_PROCESS_NO_MATCH, -+ // PIN码不正确 -+ IDENTIFY_PROCESS_PIN_INCORRECT - }; - - } // namespace Kiran -\ No newline at end of file -diff --git a/include/kiran-auth-device-i.h b/include/kiran-auth-device-i.h -index 8422eaf..89e4000 100644 ---- a/include/kiran-auth-device-i.h -+++ b/include/kiran-auth-device-i.h -@@ -26,6 +26,11 @@ extern "C" - #define GENERAL_AUTH_DEVICE_DBUS_OBJECT_PATH "/com/kylinsec/Kiran/AuthDevice/Device" - #define GENERAL_AUTH_DEVICE_DBUS_INTERFACE_NAME "com.kylinsec.Kiran.AuthDevice.Device" - -+#define AUTH_DEVICE_JSON_KEY_UKEY "ukey" -+#define AUTH_DEVICE_JSON_KEY_PIN "pin" -+#define AUTH_DEVICE_JSON_KEY_REBINDING "rebinding" -+#define AUTH_DEVICE_JSON_KEY_FEATURE_IDS "feature_ids" -+ - // 录入结果 - enum EnrollResult - { -@@ -37,6 +42,8 @@ extern "C" - ENROLL_RESULT_PASS, - // 因为扫描质量或者用户扫描过程中发生的问题引起 - ENROLL_RESULT_RETRY, -+ // UKey已经存在绑定关系 -+ ENROLL_RESULT_UKEY_EXIST_BINDING - }; - - // 识别结果 -diff --git a/include/third-party-device.h b/include/third-party-device.h -index d06a4a2..2b118d3 100644 ---- a/include/third-party-device.h -+++ b/include/third-party-device.h -@@ -19,7 +19,7 @@ namespace Kiran - { - #define ZK_ID_VENDOR "1b55" - #define SD_ID_VENDOR "05e3" -- -+#define FT_ID_VENDOR "096e" - - static const struct ThirdPartyDeviceSupported - { -@@ -35,7 +35,8 @@ static const struct ThirdPartyDeviceSupported - QString description; - }ThirdPartyDeviceSupportedTable[] = { - {DEVICE_TYPE_FingerPrint,"1b55","0120","","ZK"}, -- {DEVICE_TYPE_FingerVein,"05e3","0608","","SD"} -+ {DEVICE_TYPE_FingerVein,"05e3","0608","","SD"}, -+ {DEVICE_TYPE_UKey,"096e","0309","","FT"} - }; - - } -\ No newline at end of file -diff --git a/include/ukey-skf.h b/include/ukey-skf.h -new file mode 100644 -index 0000000..d1b6a6f ---- /dev/null -+++ b/include/ukey-skf.h -@@ -0,0 +1,325 @@ -+/** -+ * Copyright (c) 2020 ~ 2021 KylinSec Co., Ltd. -+ * kiran-biometrics is licensed under Mulan PSL v2. -+ * You can use this software according to the terms and conditions of the Mulan PSL v2. -+ * You may obtain a copy of Mulan PSL v2 at: -+ * http://license.coscl.org.cn/MulanPSL2 -+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, -+ * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, -+ * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. -+ * See the Mulan PSL v2 for more details. -+ * -+ * Author: luoqing -+ */ -+ -+#pragma once -+#include -+#include -+ -+#ifdef __cplusplus -+extern "C" -+{ -+#endif -+ -+typedef int8_t INT8; -+typedef int16_t INT16; -+typedef int32_t INT32; -+typedef unsigned char UINT8; -+typedef uint16_t UINT16; -+typedef uint32_t UINT32; -+typedef long BOOL; -+ -+typedef UINT8 BYTE; -+typedef UINT8 CHAR; -+typedef INT16 SHORT; -+typedef UINT16 USHORT; -+typedef INT32 LONG; -+typedef UINT32 ULONG; -+typedef UINT32 UINT; -+typedef UINT16 WORD; -+typedef UINT32 DWORD; -+typedef UINT32 FLAGS; -+typedef CHAR *LPSTR; -+typedef void *HANDLE; -+typedef HANDLE DEVHANDLE; -+typedef HANDLE HAPPLICATION; -+typedef HANDLE HCONTAINER; -+ -+#define MAX_RSA_MODULUS_LEN 256 -+#define MAX_RSA_EXPONENT_LEN 4 -+#define ECC_MAX_XCOORDINATE_BITS_LEN 512 -+#define ECC_MAX_YCOORDINATE_BITS_LEN 512 -+#define ECC_MAX_MODULUS_BITS_LEN 512 -+ -+#define MAX_IV_LEN 32 -+#define MAX_FILE_NAME_SIZE 32 -+#define MAX_FILE_CONTAINER_NAME_SIZE 64 -+ -+/*Permission type*/ -+#define SECURE_NEVER_ACCOUNT 0x00000000 -+#define SECURE_ADM_ACCOUNT 0x00000001 -+#define SECURE_USER_ACCOUNT 0x00000010 -+#define SECURE_ANYONE_ACCOUNT 0x000000FF -+ -+#ifndef FALSE -+#define FALSE 0x00000000 -+#endif -+ -+#ifndef TRUE -+#define TRUE 0x00000001 -+#endif -+ -+#ifndef ADMIN_TYPE -+#define ADMIN_TYPE 0 -+#endif -+ -+#ifndef USER_TYPE -+#define USER_TYPE 1 -+#endif -+ -+/* public key usage */ -+#define SGD_PK_SIGN 0x0100 -+#define SGD_PK_DH 0x0200 -+#define SGD_PK_ENC 0x0400 -+ -+/* public key types */ -+#define SGD_RSA 0x00010000 -+#define SGD_RSA_SIGN (SGD_RSA | SGD_PK_SIGN) -+#define SGD_RSA_ENC (SGD_RSA | SGD_PK_ENC) -+#define SGD_SM2 0x00020000 -+#define SGD_SM2_1 (SGD_SM2 | SGD_PK_SIGN) -+#define SGD_SM2_2 (SGD_SM2 | SGD_PK_DH) -+#define SGD_SM2_3 (SGD_SM2 | SGD_PK_ENC) -+ -+/* hash */ -+#define SGD_SM3 0x00000001 -+#define SGD_SHA1 0x00000002 -+#define SGD_SHA256 0x00000004 -+#define SGD_HASH_FROM 0x00000008 -+#define SGD_HASH_TO 0x000000FF -+ -+/* signatue schemes */ -+#define SGD_SM3_RSA (SGD_SM3|SGD_RSA) -+#define SGD_SHA1_RSA (SGD_SHA1|SGD_RSA) -+#define SGD_SHA256_RSA (SGD_SHA256|SGD_RSA) -+#define SGD_SM3_SM2 (SGD_SM3|SGD_SM2) -+#define SGD_SIG_FROM 0x00040000 -+#define SGD_SIG_TO 0x800000FF -+ -+#pragma pack(1) -+ typedef struct Struct_Version -+ { -+ BYTE major; -+ BYTE minor; -+ } VERSION; -+ -+ typedef struct Struct_DEVINFO -+ { -+ VERSION Version; -+ CHAR Manufacturer[64]; -+ CHAR Issuer[64]; -+ CHAR Label[32]; -+ CHAR SerialNumber[32]; -+ VERSION HWVersion; -+ VERSION FirmwareVersion; -+ ULONG AlgSymCap; -+ ULONG AlgAsymCap; -+ ULONG AlgHashCap; -+ ULONG DevAuthAlgId; -+ ULONG TotalSpace; -+ ULONG FreeSpace; -+ ULONG MaxECCBufferSize; -+ ULONG MaxBufferSize; -+ BYTE Reserved[64]; -+ } DEVINFO, *PDEVINFO; -+ -+ typedef struct Struct_RSAPUBLICKEYBLOB -+ { -+ ULONG AlgID; -+ ULONG BitLen; -+ BYTE Modulus[MAX_RSA_MODULUS_LEN]; -+ BYTE PublicExponent[MAX_RSA_EXPONENT_LEN]; -+ } RSAPUBLICKEYBLOB, *PRSAPUBLICKEYBLOB; -+ -+ typedef struct Struct_RSAPRIVATEKEYBLOB -+ { -+ ULONG AlgID; -+ ULONG BitLen; -+ BYTE Modulus[MAX_RSA_MODULUS_LEN]; -+ BYTE PublicExponent[MAX_RSA_EXPONENT_LEN]; -+ BYTE PrivateExponent[MAX_RSA_MODULUS_LEN]; -+ BYTE Prime1[MAX_RSA_MODULUS_LEN / 2]; -+ BYTE Prime2[MAX_RSA_MODULUS_LEN / 2]; -+ BYTE Prime1Exponent[MAX_RSA_MODULUS_LEN / 2]; -+ BYTE Prime2Exponent[MAX_RSA_MODULUS_LEN / 2]; -+ BYTE Coefficient[MAX_RSA_MODULUS_LEN / 2]; -+ } RSAPRIVATEKEYBLOB, *PRSAPRIVATEKEYBLOB; -+ -+ typedef struct Struct_ECCPUBLICKEYBLOB -+ { -+ ULONG BitLen; -+ BYTE XCoordinate[ECC_MAX_XCOORDINATE_BITS_LEN / 8]; -+ BYTE YCoordinate[ECC_MAX_YCOORDINATE_BITS_LEN / 8]; -+ } ECCPUBLICKEYBLOB, *PECCPUBLICKEYBLOB; -+ -+ typedef struct Struct_ECCPRIVATEKEYBLOB -+ { -+ ULONG BitLen; -+ BYTE PrivateKey[ECC_MAX_MODULUS_BITS_LEN / 8]; -+ } ECCPRIVATEKEYBLOB, *PECCPRIVATEKEYBLOB; -+ -+ typedef struct Struct_ECCCIPHERBLOB -+ { -+ BYTE XCoordinate[ECC_MAX_XCOORDINATE_BITS_LEN / 8]; -+ BYTE YCoordinate[ECC_MAX_XCOORDINATE_BITS_LEN / 8]; -+ BYTE HASH[32]; -+ ULONG CipherLen; -+ BYTE Cipher[1]; -+ } ECCCIPHERBLOB, *PECCCIPHERBLOB; -+ -+ typedef struct Struct_ECCSIGNATUREBLOB -+ { -+ BYTE r[ECC_MAX_XCOORDINATE_BITS_LEN / 8]; -+ BYTE s[ECC_MAX_XCOORDINATE_BITS_LEN / 8]; -+ } ECCSIGNATUREBLOB, *PECCSIGNATUREBLOB; -+ -+ typedef struct Struct_BLOCKCIPHERPARAM -+ { -+ BYTE IV[MAX_IV_LEN]; -+ ULONG IVLen; -+ ULONG PaddingType; -+ ULONG FeedBitLen; -+ } BLOCKCIPHERPARAM, *PBLOCKCIPHERPARAM; -+ -+ typedef struct SKF_ENVELOPEDKEYBLOB -+ { -+ ULONG Version; -+ ULONG ulSymmAlgID; -+ ULONG ulBits; -+ BYTE cbEncryptedPriKey[64]; -+ ECCPUBLICKEYBLOB PubKey; -+ ECCCIPHERBLOB ECCCipherBlob; -+ } ENVELOPEDKEYBLOB, *PENVELOPEDKEYBLOB; -+ -+ typedef struct Struct_FILEATTRIBUTE -+ { -+ CHAR FileName[MAX_FILE_NAME_SIZE]; -+ ULONG FileSize; -+ ULONG ReadRights; -+ ULONG WriteRights; -+ } FILEATTRIBUTE, *PFILEATTRIBUTE; -+#pragma pack() -+ -+#define SAR_OK 0x00000000 -+#define SAR_FAIL 0x0A000001 -+#define SAR_UNKNOWNERR 0x0A000002 -+#define SAR_NOTSUPPORTYETERR 0x0A000003 -+#define SAR_FILEERR 0x0A000004 -+#define SAR_INVALIDHANDLEERR 0x0A000005 -+#define SAR_INVALIDPARAMERR 0x0A000006 -+#define SAR_READFILEERR 0x0A000007 -+#define SAR_WRITEFILEERR 0x0A000008 -+#define SAR_NAMELENERR 0x0A000009 -+#define SAR_KEYUSAGEERR 0x0A00000A -+#define SAR_MODULUSLENERR 0x0A00000B -+#define SAR_NOTINITIALIZEERR 0x0A00000C -+#define SAR_OBJERR 0x0A00000D -+#define SAR_MEMORYERR 0x0A00000E -+#define SAR_TIMEOUTERR 0x0A00000F -+#define SAR_INDATALENERR 0x0A000010 -+#define SAR_INDATAERR 0x0A000011 -+#define SAR_GENRANDERR 0x0A000012 -+#define SAR_HASHOBJERR 0x0A000013 -+#define SAR_HASHERR 0x0A000014 -+#define SAR_GENRSAKEYERR 0x0A000015 -+#define SAR_RSAMODULUSLENERR 0x0A000016 -+#define SAR_CSPIMPRTPUBKEYERR 0x0A000017 -+#define SAR_RSAENCERR 0x0A000018 -+#define SAR_RSADECERR 0x0A000019 -+#define SAR_HASHNOTEQUALERR 0x0A00001A -+#define SAR_KEYNOTFOUNTERR 0x0A00001B -+#define SAR_CERTNOTFOUNTERR 0x0A00001C -+#define SAR_NOTEXPORTERR 0x0A00001D -+#define SAR_DECRYPTPADERR 0x0A00001E -+#define SAR_MACLENERR 0x0A00001F -+#define SAR_BUFFER_TOO_SMALL 0x0A000020 -+#define SAR_KEYINFOTYPEERR 0x0A000021 -+#define SAR_NOT_EVENTERR 0x0A000022 -+#define SAR_DEVICE_REMOVED 0x0A000023 -+#define SAR_PIN_INCORRECT 0x0A000024 -+#define SAR_PIN_LOCKED 0x0A000025 -+#define SAR_PIN_INVALID 0x0A000026 -+#define SAR_PIN_LEN_RANGE 0x0A000027 -+#define SAR_USER_ALREADY_LOGGED_IN 0x0A000028 -+#define SAR_USER_PIN_NOT_INITIALIZED 0x0A000029 -+#define SAR_USER_TYPE_INVALID 0x0A00002A -+#define SAR_APPLICATION_NAME_INVALID 0x0A00002B -+#define SAR_APPLICATION_EXISTS 0x0A00002C -+#define SAR_USER_NOT_LOGGED_IN 0x0A00002D -+#define SAR_APPLICATION_NOT_EXISTS 0x0A00002E -+#define SAR_FILE_ALREADY_EXIST 0x0A00002F -+#define SAR_NO_ROOM 0x0A000030 -+#define SAR_FILE_NOT_EXIST 0x0A000031 -+#define SAR_REACH_MAX_CONTAINER_COUNT 0x0A000032 -+ -+typedef struct { -+ ULONG err; -+ QString reason; -+} SKF_ERR_REASON; -+ -+static SKF_ERR_REASON skf_errors[] = { -+ { SAR_OK, "success" }, -+ { SAR_FAIL, "failure" }, -+ { SAR_UNKNOWNERR, "unknown error" }, -+ { SAR_NOTSUPPORTYETERR, "operation not supported" }, -+ { SAR_FILEERR, "file error" }, -+ { SAR_INVALIDHANDLEERR, "invalid handle" }, -+ { SAR_INVALIDPARAMERR, "invalid parameter" }, -+ { SAR_READFILEERR, "read file failure" }, -+ { SAR_WRITEFILEERR, "write file failure" }, -+ { SAR_NAMELENERR, "invalid name length" }, -+ { SAR_KEYUSAGEERR, "invalid key usage" }, -+ { SAR_MODULUSLENERR, "invalid modulus length" }, -+ { SAR_NOTINITIALIZEERR, "not initialized" }, -+ { SAR_OBJERR, "invalid object" }, -+ { SAR_MEMORYERR, "memory error" }, -+ { SAR_TIMEOUTERR, "timeout" }, -+ { SAR_INDATALENERR, "invalid input length" }, -+ { SAR_INDATAERR, "invalid input value" }, -+ { SAR_GENRANDERR, "random generation failed" }, -+ { SAR_HASHOBJERR, "invalid digest handle" }, -+ { SAR_HASHERR, "digest error" }, -+ { SAR_GENRSAKEYERR, "rsa key generation failure" }, -+ { SAR_RSAMODULUSLENERR, "invalid rsa modulus length" }, -+ { SAR_CSPIMPRTPUBKEYERR, "csp import public key error" }, -+ { SAR_RSAENCERR, "rsa encryption failure" }, -+ { SAR_RSADECERR, "rsa decryption failure" }, -+ { SAR_HASHNOTEQUALERR, "hash not equal" }, -+ { SAR_KEYNOTFOUNTERR, "key not found" }, -+ { SAR_CERTNOTFOUNTERR, "certificate not found" }, -+ { SAR_NOTEXPORTERR, "export failed" }, -+ { SAR_DECRYPTPADERR, "decrypt invalid padding" }, -+ { SAR_MACLENERR, "invalid mac length" }, -+ { SAR_BUFFER_TOO_SMALL, "buffer too small" }, -+ { SAR_KEYINFOTYPEERR, "invalid key info type" }, -+ { SAR_NOT_EVENTERR, "no event" }, -+ { SAR_DEVICE_REMOVED, "device removed" }, -+ { SAR_PIN_INCORRECT, "pin incorrect" }, -+ { SAR_PIN_LOCKED, "pin locked" }, -+ { SAR_PIN_INVALID, "invalid pin" }, -+ { SAR_PIN_LEN_RANGE, "invalid pin length" }, -+ { SAR_USER_ALREADY_LOGGED_IN, "user already logged in" }, -+ { SAR_USER_PIN_NOT_INITIALIZED, "user pin not initialized" }, -+ { SAR_USER_TYPE_INVALID, "invalid user type" }, -+ { SAR_APPLICATION_NAME_INVALID, "invalid application name" }, -+ { SAR_APPLICATION_EXISTS, "application already exist" }, -+ { SAR_USER_NOT_LOGGED_IN, "user not logged in" }, -+ { SAR_APPLICATION_NOT_EXISTS, "application not exist" }, -+ { SAR_FILE_ALREADY_EXIST, "file already exist" }, -+ { SAR_NO_ROOM, "no space" }, -+ { SAR_FILE_NOT_EXIST, "file not exist" }, -+}; -+ -+#ifdef __cplusplus -+} -+#endif -diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt -index 39c8dc5..cac6cb4 100644 ---- a/src/CMakeLists.txt -+++ b/src/CMakeLists.txt -@@ -37,11 +37,14 @@ qt_add_dbus_adaptor( - set(DEVICE_SDK ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/${PROJECT_NAME}-sdk) - - set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) --set(CMAKE_INSTALL_RPATH ${DEVICE_SDK}/finger-vein/sd:${DEVICE_SDK}/fingerprint/zk) -+set(CMAKE_INSTALL_RPATH ${DEVICE_SDK}/finger-vein/sd:${DEVICE_SDK}/fingerprint/zk:${DEVICE_SDK}/ukey/ft) - - - set(TS_FILES "${PROJECT_SOURCE_DIR}/translations/${PROJECT_NAME}.zh_CN.ts") --qt_create_translation(QM_FILES ${CMAKE_CURRENT_SOURCE_DIR} ${TS_FILES}) -+qt_create_translation(QM_FILES -+ ${CMAKE_CURRENT_SOURCE_DIR} -+ ${TS_FILES} -+ ) - - add_executable(${PROJECT_NAME} - ${SRC_H_FILES} -@@ -58,7 +61,7 @@ target_include_directories(${PROJECT_NAME} PUBLIC - ${KLOG_QT5_INCLUDE_DIRS} - ${CMAKE_CURRENT_SOURCE_DIR} - ) -- -+ - target_link_libraries(${PROJECT_NAME} - ${KLOG_QT5_LIBRARIES} - ${LIBUDEV_LIBRARIES} -diff --git a/src/auth-device-manager.cpp b/src/auth-device-manager.cpp -index 984d3c1..a0ac795 100644 ---- a/src/auth-device-manager.cpp -+++ b/src/auth-device-manager.cpp -@@ -18,9 +18,9 @@ - #include - #include - #include --#include "device/auth-device.h" - #include "auth_device_manager_adaptor.h" - #include "context/context-factory.h" -+#include "device/auth-device.h" - #include "feature-db.h" - #include "kiran-auth-device-i.h" - #include "polkit-proxy.h" -@@ -148,7 +148,7 @@ void AuthDeviceManager::onRemove(const QDBusMessage& message, const QString& fea - { - bool result = FeatureDB::getInstance()->deleteFeature(feature_id); - KLOG_DEBUG() << "deleteFeature:" << feature_id -- << "result" << result; -+ << "exec:" << result; - auto replyMessage = message.createReply(); - QDBusConnection::systemBus().send(replyMessage); - } -@@ -192,10 +192,9 @@ CHECK_AUTH_WITH_2ARGS(AuthDeviceManager, SetEnableDriver, onSetEnableDriver, AUT - - void AuthDeviceManager::init() - { -- m_dbusAdaptor = new AuthDeviceManagerAdaptor(this); -- m_contextFactory = ContextFactory::instance(); -- m_timer = new QTimer(this); -- connect(m_timer, &QTimer::timeout, this, &AuthDeviceManager::handleDeviceReCreate); -+ m_dbusAdaptor = QSharedPointer(new AuthDeviceManagerAdaptor(this)); -+ m_contextFactory = QSharedPointer(ContextFactory::instance()); -+ connect(&m_timer, &QTimer::timeout, this, &AuthDeviceManager::handleDeviceReCreate); - - QDBusConnection dbusConnection = QDBusConnection::systemBus(); - if (!dbusConnection.registerService(AUTH_DEVICE_DBUS_NAME)) -@@ -214,14 +213,14 @@ void AuthDeviceManager::init() - KLOG_ERROR() << "Can't register object:" << dbusConnection.lastError(); - } - -- m_udev = udev_new(); -+ m_udev = QSharedPointer (udev_new()); - if (!m_udev) - { - KLOG_ERROR() << "new udev error"; - } - -- initDeviceMonitor(m_udev); -- auto usbInfoList = enumerateDevices(m_udev); -+ initDeviceMonitor(m_udev.data()); -+ auto usbInfoList = enumerateDevices(m_udev.data()); - // 枚举设备后,生成设备对象 - Q_FOREACH (auto deviceInfo, usbInfoList) - { -@@ -243,16 +242,16 @@ void AuthDeviceManager::init() - void AuthDeviceManager::initDeviceMonitor(struct udev* udev) - { - // 创建一个新的monitor -- m_monitor = udev_monitor_new_from_netlink(udev, "udev"); -+ m_monitor = QSharedPointer (udev_monitor_new_from_netlink(udev, "udev")); - // 增加一个udev事件过滤器 -- udev_monitor_filter_add_match_subsystem_devtype(m_monitor, "usb", nullptr); -+ udev_monitor_filter_add_match_subsystem_devtype(m_monitor.data(), "usb", nullptr); - // 启动监控 -- udev_monitor_enable_receiving(m_monitor); -+ udev_monitor_enable_receiving(m_monitor.data()); - // 获取该监控的文件描述符,fd就代表了这个监控 -- m_fd = udev_monitor_get_fd(m_monitor); -+ m_fd = udev_monitor_get_fd(m_monitor.data()); - -- m_socketNotifierRead = new QSocketNotifier(m_fd, QSocketNotifier::Read, this); -- connect(m_socketNotifierRead, &QSocketNotifier::activated, this, &AuthDeviceManager::handleSocketNotifierRead); -+ m_socketNotifierRead = QSharedPointer(new QSocketNotifier(m_fd, QSocketNotifier::Read, this)); -+ connect(m_socketNotifierRead.data(), &QSocketNotifier::activated, this, &AuthDeviceManager::handleSocketNotifierRead); - } - - void AuthDeviceManager::handleSocketNotifierRead(int socket) -@@ -274,7 +273,7 @@ void AuthDeviceManager::handleSocketNotifierRead(int socket) - return; - - // 获取产生事件的设备映射 -- struct udev_device* dev = udev_monitor_receive_device(m_monitor); -+ struct udev_device* dev = udev_monitor_receive_device(m_monitor.data()); - if (!dev) - return; - -@@ -317,11 +316,11 @@ void AuthDeviceManager::handleSocketNotifierRead(int socket) - QList AuthDeviceManager::enumerateDevices(struct udev* udev) - { - // 创建一个枚举器用于扫描已连接的设备 -- struct udev_enumerate* enumerate = udev_enumerate_new(udev); -+ struct udev_enumerate* enumerate = udev_enumerate_new(udev); - udev_enumerate_add_match_subsystem(enumerate, SUBSYSTEM); - udev_enumerate_scan_devices(enumerate); - // 返回一个存储了设备所有属性信息的链表 -- struct udev_list_entry* devices = udev_enumerate_get_list_entry(enumerate); -+ struct udev_list_entry* devices = udev_enumerate_get_list_entry(enumerate); - struct udev_list_entry* entry; - - QList usbInfoList; -@@ -329,7 +328,7 @@ QList AuthDeviceManager::enumerateDevices(struct udev* udev) - { - const char* path = udev_list_entry_get_name(entry); - // 创建一个udev设备的映射 -- struct udev_device* dev = udev_device_new_from_syspath(udev, path); -+ struct udev_device* dev = udev_device_new_from_syspath(udev, path); - DeviceInfo usbInfo; - usbInfo.idVendor = udev_device_get_sysattr_value(dev, "idVendor"); - usbInfo.idProduct = udev_device_get_sysattr_value(dev, "idProduct"); -@@ -419,16 +418,16 @@ void AuthDeviceManager::handleDeviceCreateFail(DeviceInfo deviceInfo) - m_retreyCreateDeviceMap.insert(deviceInfo, 0); - if (m_retreyCreateDeviceMap.count() != 0) - { -- if (!m_timer->isActive()) -+ if (!m_timer.isActive()) - { -- m_timer->start(1000); -+ m_timer.start(1000); - } - } - } - - void AuthDeviceManager::handleDeviceDeleted() - { -- QList newUsbInfoList = enumerateDevices(m_udev); -+ QList newUsbInfoList = enumerateDevices(m_udev.data()); - QStringList newBusList; - Q_FOREACH (auto newUsbInfo, newUsbInfoList) - { -@@ -468,7 +467,7 @@ void AuthDeviceManager::handleDeviceReCreate() - { - if (m_retreyCreateDeviceMap.count() == 0) - { -- m_timer->stop(); -+ m_timer.stop(); - } - else - { -diff --git a/src/auth-device-manager.h b/src/auth-device-manager.h -index fcf56bd..0993a55 100644 ---- a/src/auth-device-manager.h -+++ b/src/auth-device-manager.h -@@ -15,12 +15,13 @@ - #pragma once - - #include -+#include - #include - #include -+#include - #include - #include - #include "auth-enum.h" --#include - - class AuthDeviceManagerAdaptor; - -@@ -43,7 +44,7 @@ public: - - public Q_SLOTS: - QString GetDevices(); -- QString GetDevicesByType(int device_type); -+ QString GetDevicesByType(int device_type); - QDBusObjectPath GetDevice(const QString &device_id); - QStringList GetAllFeatureIDs(); - QString GetDriversByType(int device_type); -@@ -64,8 +65,8 @@ private: - void processDevice(struct udev_device *dev); - void deviceSimpleInfo(struct udev_device *dev); - -- void onRemove(const QDBusMessage &message,const QString &feature_id); -- void onSetEnableDriver(const QDBusMessage &message,const QString &driver_name, bool enable); -+ void onRemove(const QDBusMessage &message, const QString &feature_id); -+ void onSetEnableDriver(const QDBusMessage &message, const QString &driver_name, bool enable); - - Q_SIGNALS: - void DeviceAdded(int device_type, const QString &device_id); -@@ -73,19 +74,18 @@ Q_SIGNALS: - - private: - static AuthDeviceManager *m_instance; -- struct udev *m_udev; -- struct udev_monitor *m_monitor; -+ QSharedPointer m_udev; -+ QSharedPointer m_monitor; - - int m_fd; -- AuthDeviceManagerAdaptor *m_dbusAdaptor; -- QSocketNotifier *m_socketNotifierRead; -+ QSharedPointer m_dbusAdaptor; -+ QSharedPointer m_socketNotifierRead; - // 总线 -- AuthDevice对象对应 - QMap m_deviceMap; -- ContextFactory *m_contextFactory; -- -- QTimer *m_timer; -+ QSharedPointer m_contextFactory; -+ QTimer m_timer; - - // 设备信息-重试次数 -- QMap m_retreyCreateDeviceMap; -+ QMap m_retreyCreateDeviceMap; - }; - } // namespace Kiran -diff --git a/src/context/context-factory.cpp b/src/context/context-factory.cpp -index 82c9c6b..f489d8f 100644 ---- a/src/context/context-factory.cpp -+++ b/src/context/context-factory.cpp -@@ -15,11 +15,12 @@ - #include "context-factory.h" - #include - #include -+#include "finger-vein/fv-sd-context.h" - #include "fingerprint/fp-builtin-context.h" - #include "fingerprint/fp-zk-context.h" --#include "finger-vein/fv-sd-context.h" - #include "kiran-auth-device-i.h" - #include "third-party-device.h" -+#include "ukey/ukey-ft-context.h" - - namespace Kiran - { -@@ -47,9 +48,10 @@ ContextFactory::ContextFactory(QObject* parent) - - void ContextFactory::init() - { -- m_fpZKContext = new FPZKContext(); -- m_fpBuiltInContext = new FPBuiltInContext(); -- m_fvSDContext = new FVSDContext(); -+ m_fpZKContext = QSharedPointer(new FPZKContext()); -+ m_fpBuiltInContext = QSharedPointer(new FPBuiltInContext()); -+ m_fvSDContext = QSharedPointer(new FVSDContext()); -+ m_ukeyFTContext = QSharedPointer(new UKeyFTContext()); - } - - AuthDevice* ContextFactory::createDevice(const QString& idVendor, const QString& idProduct) -@@ -72,6 +74,9 @@ AuthDevice* ContextFactory::createDevice(const QString& idVendor, const QString& - case DEVICE_TYPE_FingerVein: - device = createFingerVeinDevice(idVendor, idProduct); - break; -+ case DEVICE_TYPE_UKey: -+ device = createUKeyDevice(idVendor, idProduct); -+ break; - default: - break; - } -@@ -120,6 +125,18 @@ AuthDevice* ContextFactory::createFingerVeinDevice(const QString& idVendor, cons - return nullptr; - } - -+AuthDevice* ContextFactory::createUKeyDevice(const QString& idVendor, const QString& idProduct) -+{ -+ if (idVendor == FT_ID_VENDOR) -+ { -+ return m_ukeyFTContext->createDevice(idVendor, idProduct); -+ } -+ else -+ { -+ return nullptr; -+ }; -+} -+ - Context* ContextFactory::CreateContext() - { - return nullptr; -diff --git a/src/context/context-factory.h b/src/context/context-factory.h -index f2d510a..5d06839 100644 ---- a/src/context/context-factory.h -+++ b/src/context/context-factory.h -@@ -15,13 +15,14 @@ - - #include - #include "context.h" -- -+#include - - namespace Kiran - { - class FPZKContext; - class FPBuiltInContext; - class FVSDContext; -+class UKeyFTContext; - - class ContextFactory : public QObject - { -@@ -43,11 +44,13 @@ private: - void init(); - AuthDevice* createFingerPrintDevice(const QString& idVendor, const QString& idProduct); - AuthDevice* createFingerVeinDevice(const QString& idVendor, const QString& idProduct); -+ AuthDevice* createUKeyDevice(const QString& idVendor, const QString& idProduct); - - private: - QStringList m_idVendorList; -- FPZKContext* m_fpZKContext; -- FPBuiltInContext* m_fpBuiltInContext; -- FVSDContext* m_fvSDContext; -+ QSharedPointer m_fpZKContext; -+ QSharedPointer m_fpBuiltInContext; -+ QSharedPointer m_fvSDContext; -+ QSharedPointer m_ukeyFTContext; - }; - } // namespace Kiran -diff --git a/src/context/finger-vein/fv-sd-context.cpp b/src/context/finger-vein/fv-sd-context.cpp -index a2e9dc7..d898564 100644 ---- a/src/context/finger-vein/fv-sd-context.cpp -+++ b/src/context/finger-vein/fv-sd-context.cpp -@@ -24,6 +24,7 @@ FVSDContext::FVSDContext(QObject* parent) - { - } - -+//TODO:createDevice 流程类似,考虑优化,减少重复代码 - // fp-zk-context 需要管理多个so,可以生成不同 so的设备 - AuthDevice* FVSDContext::createDevice(const QString& idVendor, const QString& idProduct) - { -diff --git a/src/context/ukey/ukey-ft-context.cpp b/src/context/ukey/ukey-ft-context.cpp -new file mode 100644 -index 0000000..40e9df3 ---- /dev/null -+++ b/src/context/ukey/ukey-ft-context.cpp -@@ -0,0 +1,57 @@ -+/** -+ * Copyright (c) 2020 ~ 2021 KylinSec Co., Ltd. -+ * kiran-biometrics is licensed under Mulan PSL v2. -+ * You can use this software according to the terms and conditions of the Mulan PSL v2. -+ * You may obtain a copy of Mulan PSL v2 at: -+ * http://license.coscl.org.cn/MulanPSL2 -+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, -+ * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, -+ * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. -+ * See the Mulan PSL v2 for more details. -+ * -+ * Author: luoqing -+ */ -+ -+#include "ukey-ft-context.h" -+#include -+#include "device/ukey/ukey-ft-device.h" -+#include "utils.h" -+ -+namespace Kiran -+{ -+UKeyFTContext::UKeyFTContext(QObject* parent) -+ : Context{parent} -+{ -+} -+ -+AuthDevice* UKeyFTContext::createDevice(const QString& idVendor, const QString& idProduct) -+{ -+ auto ftDevice = new UKeyFTDevice(); -+ if (!Utils::driverEnabled(ftDevice->deviceDriver())) -+ { -+ KLOG_INFO() << QString("driver %1 is disabled! device %2:%3 can't be used") -+ .arg(ftDevice->deviceDriver()) -+ .arg(idVendor) -+ .arg(idProduct); -+ return nullptr; -+ } -+ if (ftDevice->init()) -+ { -+ QString deviceName = Utils::getDeviceName(idVendor, idProduct); -+ if (deviceName.isEmpty()) -+ { -+ deviceName = "Feitian Technologies"; -+ } -+ ftDevice->setDeviceName(deviceName); -+ ftDevice->setDeviceInfo(idVendor, idProduct); -+ m_deviceMap.insert(ftDevice->deviceID(), ftDevice); -+ return ftDevice; -+ } -+ else -+ { -+ KLOG_ERROR() << QString("device %1:%2 init failed!").arg(idVendor).arg(idProduct); -+ ftDevice->deleteLater(); -+ return nullptr; -+ } -+} -+} // namespace Kiran -diff --git a/src/context/ukey/ukey-ft-context.h b/src/context/ukey/ukey-ft-context.h -new file mode 100644 -index 0000000..48bd40d ---- /dev/null -+++ b/src/context/ukey/ukey-ft-context.h -@@ -0,0 +1,27 @@ -+/** -+ * Copyright (c) 2020 ~ 2021 KylinSec Co., Ltd. -+ * kiran-biometrics is licensed under Mulan PSL v2. -+ * You can use this software according to the terms and conditions of the Mulan PSL v2. -+ * You may obtain a copy of Mulan PSL v2 at: -+ * http://license.coscl.org.cn/MulanPSL2 -+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, -+ * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, -+ * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. -+ * See the Mulan PSL v2 for more details. -+ * -+ * Author: luoqing -+ */ -+#pragma once -+#include "context/context.h" -+ -+namespace Kiran -+{ -+class AuthDevice; -+class UKeyFTContext : public Context -+{ -+public: -+ explicit UKeyFTContext(QObject *parent = nullptr); -+ AuthDevice* createDevice(const QString& idVendor, const QString& idProduct) override; -+}; -+ -+} // namespace Kiran -diff --git a/src/device/auth-device.cpp b/src/device/auth-device.cpp -index 0b890ec..d906fd1 100644 ---- a/src/device/auth-device.cpp -+++ b/src/device/auth-device.cpp -@@ -42,7 +42,7 @@ bool AuthDevice::init() - { - if (initDevice()) - { -- m_dbusAdaptor = new AuthDeviceAdaptor(this); -+ m_dbusAdaptor = QSharedPointer(new AuthDeviceAdaptor(this)); - m_deviceID = QUuid::createUuid().toString(QUuid::WithoutBraces); - m_deviceStatus = DEVICE_STATUS_IDLE; - registerDBusObject(); -@@ -71,17 +71,17 @@ void AuthDevice::registerDBusObject() - - void AuthDevice::initFutureWatcher() - { -- m_futureWatcher = new QFutureWatcher(this); -- connect(m_futureWatcher, &QFutureWatcher::finished, this, &AuthDevice::handleAcquiredFeature); -+ m_futureWatcher = QSharedPointer>(new QFutureWatcher(this)); -+ connect(m_futureWatcher.data(), &QFutureWatcher::finished, this, &AuthDevice::handleAcquiredFeature); - connect(this, &AuthDevice::retry, this, &AuthDevice::handleRetry); - } - - void AuthDevice::initServiceWatcher() - { -- m_serviceWatcher = new QDBusServiceWatcher(this); -+ m_serviceWatcher = QSharedPointer(new QDBusServiceWatcher(this)); - this->m_serviceWatcher->setConnection(QDBusConnection::systemBus()); - this->m_serviceWatcher->setWatchMode(QDBusServiceWatcher::WatchForUnregistration); -- connect(m_serviceWatcher, &QDBusServiceWatcher::serviceUnregistered, this, &AuthDevice::onNameLost); -+ connect(m_serviceWatcher.data(), &QDBusServiceWatcher::serviceUnregistered, this, &AuthDevice::onNameLost); - } - - void AuthDevice::onNameLost(const QString& serviceName) -@@ -110,6 +110,15 @@ void AuthDevice::clearWatchedServices() - } - } - -+DeviceInfo AuthDevice::deviceInfo() -+{ -+ DeviceInfo deviceInfo; -+ deviceInfo.idVendor = m_idVendor; -+ deviceInfo.idProduct = m_idProduct; -+ deviceInfo.busPath = ""; -+ return deviceInfo; -+} -+ - void AuthDevice::setDeviceType(DeviceType deviceType) - { - m_deviceType = deviceType; -@@ -153,8 +162,36 @@ void AuthDevice::onEnrollStart(const QDBusMessage& dbusMessage, const QString& e - KLOG_DEBUG() << message; - return; - } -+ QJsonValue ukeyValue = Utils::getValueFromJsonString(extraInfo, AUTH_DEVICE_JSON_KEY_UKEY); -+ if (ukeyValue.isNull()) -+ { -+ onBioEnrollStart(dbusMessage); -+ } -+ else -+ { -+ onUKeyEnrollStart(dbusMessage, ukeyValue); -+ } -+} - -- // 获取当前保存的指纹模板,判断是否达到最大指纹数目 -+void AuthDevice::onEnrollStop(const QDBusMessage& dbusMessage) -+{ -+ if (deviceStatus() == DEVICE_STATUS_DOING_ENROLL) -+ { -+ acquireFeatureStop(); -+ m_enrollTemplates.clear(); -+ setDeviceStatus(DEVICE_STATUS_IDLE); -+ clearWatchedServices(); -+ KLOG_DEBUG() << "EnrollStop"; -+ } -+ -+ auto replyMessage = dbusMessage.createReply(); -+ QDBusConnection::systemBus().send(replyMessage); -+} -+ -+void AuthDevice::onBioEnrollStart(const QDBusMessage& dbusMessage) -+{ -+ QString message; -+ // 获取当前保存的特征模板,判断是否达到最大数目 - QByteArrayList saveList = FeatureDB::getInstance()->getFeatures(m_idVendor, m_idProduct); - if (saveList.count() == TEMPLATE_MAX_NUMBER) - { -@@ -163,32 +200,35 @@ void AuthDevice::onEnrollStart(const QDBusMessage& dbusMessage, const QString& e - KLOG_ERROR() << message; - return; - } -- - setDeviceStatus(DEVICE_STATUS_DOING_ENROLL); -- m_doEnroll = true; -- - auto future = QtConcurrent::run(this, &AuthDevice::acquireFeature); - m_futureWatcher->setFuture(future); - m_serviceWatcher->addWatchedService(dbusMessage.service()); -- - auto replyMessage = dbusMessage.createReply(); - QDBusConnection::systemBus().send(replyMessage); - } - --void AuthDevice::onEnrollStop(const QDBusMessage& dbusMessage) -+void AuthDevice::onUKeyEnrollStart(const QDBusMessage& dbusMessage, QJsonValue ukeyValue) - { -- if (deviceStatus() == DEVICE_STATUS_DOING_ENROLL) -+ QString message; -+ auto jsonObject = ukeyValue.toObject(); -+ m_pin = jsonObject.value(AUTH_DEVICE_JSON_KEY_PIN).toString(); -+ bool rebinding = jsonObject.value(AUTH_DEVICE_JSON_KEY_REBINDING).toBool(); -+ if (m_pin.isEmpty()) - { -- acquireFeatureStop(); -- m_doEnroll = false; -- m_enrollTemplates.clear(); -- setDeviceStatus(DEVICE_STATUS_IDLE); -- clearWatchedServices(); -- KLOG_DEBUG() << "EnrollStop"; -+ message = tr("The pin code cannot be empty!"); -+ Q_EMIT m_dbusAdaptor->EnrollStatus("", 0, ENROLL_RESULT_FAIL, message); -+ KLOG_ERROR() << message; -+ return; -+ } -+ else -+ { -+ setDeviceStatus(DEVICE_STATUS_DOING_ENROLL); -+ doingUKeyEnrollStart(m_pin, rebinding); -+ m_serviceWatcher->addWatchedService(dbusMessage.service()); -+ auto replyMessage = dbusMessage.createReply(); -+ QDBusConnection::systemBus().send(replyMessage); - } -- -- auto replyMessage = dbusMessage.createReply(); -- QDBusConnection::systemBus().send(replyMessage); - } - - void AuthDevice::onIdentifyStart(const QDBusMessage& dbusMessage, const QString& value) -@@ -202,10 +242,7 @@ void AuthDevice::onIdentifyStart(const QDBusMessage& dbusMessage, const QString& - return; - } - -- setDeviceStatus(DEVICE_STATUS_DOING_IDENTIFY); -- m_doIdentify = true; -- -- QJsonArray jsonArray = Utils::getValueFromJsonString(value, "feature_ids").toArray(); -+ QJsonArray jsonArray = Utils::getValueFromJsonString(value, AUTH_DEVICE_JSON_KEY_FEATURE_IDS).toArray(); - if (!jsonArray.isEmpty()) - { - QVariantList varList = jsonArray.toVariantList(); -@@ -214,12 +251,16 @@ void AuthDevice::onIdentifyStart(const QDBusMessage& dbusMessage, const QString& - m_identifyIDs << var.toString(); - } - } -- auto future = QtConcurrent::run(this, &AuthDevice::acquireFeature); -- m_futureWatcher->setFuture(future); -- m_serviceWatcher->addWatchedService(dbusMessage.service()); - -- auto replyMessage = dbusMessage.createReply(); -- QDBusConnection::systemBus().send(replyMessage); -+ QJsonValue ukeyValue = Utils::getValueFromJsonString(value, AUTH_DEVICE_JSON_KEY_UKEY); -+ if (ukeyValue.isUndefined()) -+ { -+ onBioIdentifyStart(dbusMessage); -+ } -+ else -+ { -+ onUKeyIdentifyStart(dbusMessage, ukeyValue); -+ } - } - - void AuthDevice::onIdentifyStop(const QDBusMessage& message) -@@ -227,7 +268,6 @@ void AuthDevice::onIdentifyStop(const QDBusMessage& message) - if (deviceStatus() == DEVICE_STATUS_DOING_IDENTIFY) - { - acquireFeatureStop(); -- m_doIdentify = false; - m_identifyIDs.clear(); - setDeviceStatus(DEVICE_STATUS_IDLE); - clearWatchedServices(); -@@ -237,90 +277,49 @@ void AuthDevice::onIdentifyStop(const QDBusMessage& message) - QDBusConnection::systemBus().send(replyMessage); - } - --CHECK_AUTH_WITH_1ARGS(AuthDevice, EnrollStart, onEnrollStart, AUTH_USER_ADMIN, const QString&) --CHECK_AUTH_WITH_1ARGS(AuthDevice, IdentifyStart, onIdentifyStart, AUTH_USER_ADMIN, const QString&) --CHECK_AUTH(AuthDevice, EnrollStop, onEnrollStop, AUTH_USER_ADMIN) --CHECK_AUTH(AuthDevice, IdentifyStop, onIdentifyStop, AUTH_USER_ADMIN) -- --QStringList AuthDevice::GetFeatureIDList() -+void AuthDevice::onBioIdentifyStart(const QDBusMessage& dbusMessage) - { -- QStringList featureIDs = FeatureDB::getInstance()->getFeatureIDs(m_idVendor, m_idProduct); -- return featureIDs; -+ setDeviceStatus(DEVICE_STATUS_DOING_IDENTIFY); -+ auto future = QtConcurrent::run(this, &AuthDevice::acquireFeature); -+ m_futureWatcher->setFuture(future); -+ -+ m_serviceWatcher->addWatchedService(dbusMessage.service()); -+ auto replyMessage = dbusMessage.createReply(); -+ QDBusConnection::systemBus().send(replyMessage); - } - --void AuthDevice::doingEnrollProcess(QByteArray feature) -+void AuthDevice::onUKeyIdentifyStart(const QDBusMessage& dbusMessage, QJsonValue ukeyValue) - { -- if (!m_doEnroll) -- { -- return; -- } - QString message; -- if (feature.isEmpty()) -- { -- acquireFeatureFail(); -- return; -- } -+ auto jsonObject = ukeyValue.toObject(); -+ m_pin = jsonObject.value(AUTH_DEVICE_JSON_KEY_PIN).toString(); - -- int templatesCount = enrollTemplatesFromCache().count(); -- if (templatesCount == 0) -+ if (m_pin.isEmpty()) - { -- // 第一个指纹模板入录时,检查该指纹是否录入过 -- QString featureID = isFeatureEnrolled(feature); -- if (featureID.isEmpty()) -- { -- saveEnrollTemplateToCache(feature); -- enrollProcessRetry(); -- } -- else -- { -- notifyEnrollProcess(ENROLL_PROCESS_REPEATED_ENROLL, featureID); -- internalStopEnroll(); -- } -- } -- else if (templatesCount < needTemplatesCountForEnroll()) -- { -- // 判断录入时是否录的是同一根手指 -- int matchResult = templateMatch(m_enrollTemplates.value(0), feature); -- if (matchResult == GENERAL_RESULT_OK) -- { -- saveEnrollTemplateToCache(feature); -- } -- else -- { -- notifyEnrollProcess(ENROLL_PROCESS_INCONSISTENT_FEATURE); -- } -- enrollProcessRetry(); -- } -- else if (enrollTemplatesFromCache().count() == needTemplatesCountForEnroll()) -- { -- enrollTemplateMerge(); -- } --} -- --void AuthDevice::doingIdentifyProcess(QByteArray feature) --{ -- if (!m_doIdentify) -- { -- KLOG_DEBUG() << "device busy"; -- return; -- } -- if (feature.isEmpty()) -- { -- acquireFeatureFail(); -+ message = tr("The pin code cannot be empty!"); -+ Q_EMIT m_dbusAdaptor->IdentifyStatus("", IDENTIFY_RESULT_NOT_MATCH, message); -+ KLOG_ERROR() << message; - return; - } -- -- QString featureID = identifyFeature(feature, m_identifyIDs); -- if (!featureID.isEmpty()) -- { -- notifyIdentifyProcess(IDENTIFY_PROCESS_MACTCH,featureID); -- } - else - { -- notifyIdentifyProcess(IDENTIFY_PROCESS_NO_MATCH); -+ setDeviceStatus(DEVICE_STATUS_DOING_IDENTIFY); -+ doingUKeyIdentifyStart(m_pin); -+ m_serviceWatcher->addWatchedService(dbusMessage.service()); -+ auto replyMessage = dbusMessage.createReply(); -+ QDBusConnection::systemBus().send(replyMessage); - } -+} -+ -+CHECK_AUTH_WITH_1ARGS(AuthDevice, EnrollStart, onEnrollStart, AUTH_USER_ADMIN, const QString&) -+CHECK_AUTH_WITH_1ARGS(AuthDevice, IdentifyStart, onIdentifyStart, AUTH_USER_ADMIN, const QString&) -+CHECK_AUTH(AuthDevice, EnrollStop, onEnrollStop, AUTH_USER_ADMIN) -+CHECK_AUTH(AuthDevice, IdentifyStop, onIdentifyStop, AUTH_USER_ADMIN) - -- internalStopIdentify(); -+QStringList AuthDevice::GetFeatureIDList() -+{ -+ QStringList featureIDs = FeatureDB::getInstance()->getFeatureIDs(m_idVendor, m_idProduct); -+ return featureIDs; - } - - void AuthDevice::internalStopEnroll() -@@ -328,7 +327,6 @@ void AuthDevice::internalStopEnroll() - if (deviceStatus() == DEVICE_STATUS_DOING_ENROLL) - { - acquireFeatureStop(); -- m_doEnroll = false; - m_enrollTemplates.clear(); - setDeviceStatus(DEVICE_STATUS_IDLE); - clearWatchedServices(); -@@ -341,7 +339,6 @@ void AuthDevice::internalStopIdentify() - if (deviceStatus() == DEVICE_STATUS_DOING_IDENTIFY) - { - acquireFeatureStop(); -- m_doIdentify = false; - m_identifyIDs.clear(); - setDeviceStatus(DEVICE_STATUS_IDLE); - clearWatchedServices(); -@@ -349,28 +346,15 @@ void AuthDevice::internalStopIdentify() - } - } - --void AuthDevice::enrollProcessRetry() --{ -- Q_EMIT this->retry(); --} -- --void AuthDevice::saveEnrollTemplateToCache(QByteArray enrollTemplate) -+void AuthDevice::handleAcquiredFeature() - { -- if (!enrollTemplate.isEmpty()) -+ QByteArray feature = m_futureWatcher->result(); -+ if (feature.isEmpty()) - { -- m_enrollTemplates << enrollTemplate; -- KLOG_DEBUG() << "enroll template:" << enrollTemplate; -- notifyEnrollProcess(ENROLL_PROCESS_PASS); -+ acquireFeatureFail(); -+ return; - } --} -- --QByteArrayList AuthDevice::enrollTemplatesFromCache() --{ -- return m_enrollTemplates; --} - --void AuthDevice::handleAcquiredFeature() --{ - switch (deviceStatus()) - { - case DEVICE_STATUS_DOING_ENROLL: -diff --git a/src/device/auth-device.h b/src/device/auth-device.h -index 03997bc..0d9a9c8 100644 ---- a/src/device/auth-device.h -+++ b/src/device/auth-device.h -@@ -14,7 +14,6 @@ - - #pragma once - --// #include "auth_device_adaptor.h" - #include - #include - #include -@@ -22,12 +21,13 @@ - #include - #include "auth-enum.h" - #include "kiran-auth-device-i.h" -+#include - - class AuthDeviceAdaptor; - - namespace Kiran - { --typedef void* Handle; -+typedef void *Handle; - class BDriver; - - class AuthDevice : public QObject, protected QDBusContext -@@ -40,19 +40,21 @@ class AuthDevice : public QObject, protected QDBusContext - - public: - explicit AuthDevice(QObject *parent = nullptr); -+ - virtual ~AuthDevice(); - bool init(); - - virtual bool initDevice() = 0; - virtual BDriver *getDriver() = 0; -- QDBusObjectPath getObjectPath() { return m_objectPath; }; - -+ QDBusObjectPath getObjectPath() { return m_objectPath; }; - QString deviceID() { return m_deviceID; }; - QString deviceDriver() { return m_deviceDriver; }; - int deviceType() { return m_deviceType; }; - int deviceStatus() { return m_deviceStatus; } -- - QString deviceName() { return m_deviceName; }; -+ DeviceInfo deviceInfo(); -+ - void setDeviceType(DeviceType deviceType); - void setDeviceStatus(DeviceStatus deviceStatus); - void setDeviceName(const QString &deviceName); -@@ -71,24 +73,6 @@ protected: - virtual void acquireFeatureStop() = 0; - virtual void acquireFeatureFail() = 0; - -- virtual void doingEnrollProcess(QByteArray feature); -- virtual void doingIdentifyProcess(QByteArray feature); -- -- virtual void enrollProcessFail(const QString &featureID){}; -- virtual void enrollProcessRetry(); -- virtual void enrollTemplateMerge() = 0; -- -- virtual QString isFeatureEnrolled(QByteArray fpTemplate) = 0; -- virtual QString identifyFeature(QByteArray feature, QStringList featureIDs) = 0; -- virtual int templateMatch(QByteArray fpTemplate1, QByteArray fpTemplate2) = 0; -- -- virtual void saveEnrollTemplateToCache(QByteArray enrollTemplate); -- QByteArrayList enrollTemplatesFromCache(); -- -- virtual void notifyEnrollProcess(EnrollProcess process, const QString &featureID = QString()) = 0; -- virtual void notifyIdentifyProcess(IdentifyProcess process, const QString &featureID = QString()) = 0; -- virtual int needTemplatesCountForEnroll() = 0; -- - private Q_SLOTS: - void onNameLost(const QString &serviceName); - void handleAcquiredFeature(); -@@ -96,8 +80,8 @@ private Q_SLOTS: - - protected: - void clearWatchedServices(); -- void internalStopEnroll(); -- void internalStopIdentify(); -+ virtual void internalStopEnroll(); -+ virtual void internalStopIdentify(); - - private: - void registerDBusObject(); -@@ -106,33 +90,42 @@ private: - - void onEnrollStart(const QDBusMessage &message, const QString &extraInfo); - void onEnrollStop(const QDBusMessage &message); -+ void onBioEnrollStart(const QDBusMessage &dbusMessage); -+ void onUKeyEnrollStart(const QDBusMessage &dbusMessage, QJsonValue jsonValue); -+ - void onIdentifyStart(const QDBusMessage &message, const QString &value); - void onIdentifyStop(const QDBusMessage &message); -+ void onBioIdentifyStart(const QDBusMessage &dbusMessage); -+ void onUKeyIdentifyStart(const QDBusMessage &dbusMessage, QJsonValue jsonValue); -+ -+ virtual void doingEnrollProcess(QByteArray feature){}; -+ virtual void doingIdentifyProcess(QByteArray feature){}; -+ -+ virtual void doingUKeyEnrollStart(const QString &pin, bool rebinding = false){}; -+ virtual void doingUKeyIdentifyStart(const QString &pin){}; - - Q_SIGNALS: - void retry(); - - protected: -- QString m_deviceID; - QString m_deviceDriver; -+ QSharedPointer m_dbusAdaptor; -+ bool m_doAcquire = true; -+ QStringList m_identifyIDs; -+ QByteArrayList m_enrollTemplates; -+ QSharedPointer> m_futureWatcher; -+ QString m_pin; -+ -+private: -+ QString m_deviceID; - int m_deviceType; - int m_deviceStatus; - QString m_deviceName; - QString m_idVendor; - QString m_idProduct; - QDBusObjectPath m_objectPath; -- QDBusServiceWatcher *m_serviceWatcher; -- AuthDeviceAdaptor *m_dbusAdaptor; -+ QSharedPointer m_serviceWatcher; - -- bool m_doAcquire = true; -- bool m_doIdentify = true; -- bool m_doEnroll = true; -- QStringList m_identifyIDs; -- QByteArrayList m_enrollTemplates; -- -- QFutureWatcher *m_futureWatcher; -- --private: - /** - * 用于注册com.kylinsec.Kiran.AuthDevice.Device服务时的编号 - * 在生成AuthDevice对象,注册dbus服务成功后,数值加1 -diff --git a/src/device/bio-device.cpp b/src/device/bio-device.cpp -new file mode 100644 -index 0000000..7342928 ---- /dev/null -+++ b/src/device/bio-device.cpp -@@ -0,0 +1,104 @@ -+/** -+ * Copyright (c) 2020 ~ 2021 KylinSec Co., Ltd. -+ * kiran-biometrics is licensed under Mulan PSL v2. -+ * You can use this software according to the terms and conditions of the Mulan PSL v2. -+ * You may obtain a copy of Mulan PSL v2 at: -+ * http://license.coscl.org.cn/MulanPSL2 -+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, -+ * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, -+ * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. -+ * See the Mulan PSL v2 for more details. -+ * -+ * Author: luoqing -+ */ -+ -+#include "bio-device.h" -+#include -+namespace Kiran -+{ -+BioDevice::BioDevice(QObject *parent) : AuthDevice{parent} -+{ -+} -+ -+BioDevice::~BioDevice() -+{ -+} -+ -+void BioDevice::doingEnrollProcess(QByteArray feature) -+{ -+ int templatesCount = enrollTemplatesFromCache().count(); -+ if (templatesCount == 0) -+ { -+ // 第一个指纹模板入录时,检查该指纹是否录入过 -+ QString featureID = isFeatureEnrolled(feature); -+ if (featureID.isEmpty()) -+ { -+ saveEnrollTemplateToCache(feature); -+ enrollProcessRetry(); -+ } -+ else -+ { -+ notifyEnrollProcess(ENROLL_PROCESS_REPEATED_ENROLL, featureID); -+ internalStopEnroll(); -+ } -+ } -+ else if (templatesCount < mergeTemplateCount()) -+ { -+ // 判断录入时是否录的是同一根手指 -+ int matchResult = templateMatch(m_enrollTemplates.value(0), feature); -+ if (matchResult == GENERAL_RESULT_OK) -+ { -+ saveEnrollTemplateToCache(feature); -+ } -+ else -+ { -+ notifyEnrollProcess(ENROLL_PROCESS_INCONSISTENT_FEATURE); -+ } -+ enrollProcessRetry(); -+ } -+ else if (enrollTemplatesFromCache().count() == mergeTemplateCount()) -+ { -+ enrollTemplateMerge(); -+ } -+} -+ -+void BioDevice::doingIdentifyProcess(QByteArray feature) -+{ -+ QString featureID = identifyFeature(feature, m_identifyIDs); -+ if (!featureID.isEmpty()) -+ { -+ notifyIdentifyProcess(IDENTIFY_PROCESS_MACTCH, featureID); -+ } -+ else -+ { -+ notifyIdentifyProcess(IDENTIFY_PROCESS_NO_MATCH); -+ } -+ -+ internalStopIdentify(); -+} -+QByteArrayList BioDevice::enrollTemplatesFromCache() -+{ -+ return m_enrollTemplates; -+} -+ -+void BioDevice::saveEnrollTemplateToCache(QByteArray enrollTemplate) -+{ -+ if (!enrollTemplate.isEmpty()) -+ { -+ m_enrollTemplates << enrollTemplate; -+ KLOG_DEBUG() << "enroll template:" << enrollTemplate; -+ notifyEnrollProcess(ENROLL_PROCESS_PASS); -+ } -+} -+ -+void BioDevice::enrollProcessRetry() -+{ -+ Q_EMIT this->retry(); -+} -+ -+QString BioDevice::isFeatureEnrolled(QByteArray fpTemplate) -+{ -+ return identifyFeature(fpTemplate, QStringList()); -+} -+ -+} // namespace Kiran -\ No newline at end of file -diff --git a/src/device/bio-device.h b/src/device/bio-device.h -new file mode 100644 -index 0000000..c55aa84 ---- /dev/null -+++ b/src/device/bio-device.h -@@ -0,0 +1,47 @@ -+/** -+ * Copyright (c) 2020 ~ 2021 KylinSec Co., Ltd. -+ * kiran-biometrics is licensed under Mulan PSL v2. -+ * You can use this software according to the terms and conditions of the Mulan PSL v2. -+ * You may obtain a copy of Mulan PSL v2 at: -+ * http://license.coscl.org.cn/MulanPSL2 -+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, -+ * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, -+ * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. -+ * See the Mulan PSL v2 for more details. -+ * -+ * Author: luoqing -+ */ -+#pragma once -+#include "auth-device.h" -+ -+namespace Kiran -+{ -+class BioDevice : public AuthDevice -+{ -+ Q_OBJECT -+public: -+ explicit BioDevice(QObject *parent = nullptr); -+ ~BioDevice(); -+ -+protected: -+ void doingEnrollProcess(QByteArray feature) override; -+ void doingIdentifyProcess(QByteArray feature) override; -+ -+ virtual void enrollProcessRetry(); -+ -+ virtual void enrollTemplateMerge() = 0; -+ virtual int templateMatch(QByteArray fpTemplate1, QByteArray fpTemplate2) = 0; -+ virtual QString identifyFeature(QByteArray feature, QStringList featureIDs) = 0; -+ -+ virtual int mergeTemplateCount() = 0; -+ virtual void notifyEnrollProcess(EnrollProcess process, const QString &featureID = QString()) = 0; -+ virtual void notifyIdentifyProcess(IdentifyProcess process, const QString &featureID = QString()) = 0; -+ -+ QByteArrayList enrollTemplatesFromCache(); -+ virtual void saveEnrollTemplateToCache(QByteArray enrollTemplate); -+ -+private: -+ virtual QString isFeatureEnrolled(QByteArray fpTemplate); -+}; -+ -+} // namespace Kiran -diff --git a/src/device/finger-vein/fv-sd-device.cpp b/src/device/finger-vein/fv-sd-device.cpp -index 43faad7..5ae6605 100644 ---- a/src/device/finger-vein/fv-sd-device.cpp -+++ b/src/device/finger-vein/fv-sd-device.cpp -@@ -111,7 +111,7 @@ struct DriverLib - TGSetDevLedFunc TGSetDevLed; - }; - --FVSDDevice::FVSDDevice(QObject *parent) : AuthDevice{parent}, -+FVSDDevice::FVSDDevice(QObject *parent) : BioDevice{parent}, - m_driverLib(nullptr), - m_libProcessHandle(nullptr), - m_libComHandle(nullptr) -@@ -125,17 +125,10 @@ FVSDDevice::~FVSDDevice() - if (!deviceID().isEmpty()) - { - acquireFeatureStop(); -- m_futureWatcher->deleteLater(); - KLOG_DEBUG() << "TGGetDevStatus():" << m_driverLib->TGGetDevStatus(); - m_driverLib->TGCloseDev(); - } - -- if (m_driverLib) -- { -- delete m_driverLib; -- m_driverLib = nullptr; -- } -- - if (m_libComHandle) - { - dlclose(m_libComHandle); -@@ -166,7 +159,7 @@ bool FVSDDevice::loadLib() - return false; - } - -- m_driverLib = new DriverLib; -+ m_driverLib = QSharedPointer(new DriverLib); - m_driverLib->TGInitFVProcess = (TGInitFVProcessFunc)dlsym(m_libProcessHandle, "TGInitFVProcess"); - m_driverLib->TGImgExtractFeatureVerify = (TGImgExtractFeatureVerifyFunc)dlsym(m_libProcessHandle, "TGImgExtractFeatureVerify"); - m_driverLib->TGFeaturesFusionTmpl = (TGFeaturesFusionTmplFunc)dlsym(m_libProcessHandle, "TGFeaturesFusionTmpl"); -@@ -221,11 +214,11 @@ bool FVSDDevice::initDevice() - } - KLOG_DEBUG() << "Device opened successfully:" << ret; - -- ret = m_driverLib->TGGetDevFW(fw); -+ m_driverLib->TGGetDevFW(fw); - KLOG_DEBUG() << "Get firmware version:" << fw; -- ret = m_driverLib->TGGetDevSN(sn); -+ m_driverLib->TGGetDevSN(sn); - KLOG_DEBUG() << "Obtain device SN number:" << sn; -- ret = m_driverLib->TGPlayDevVoice(VOICE_VOLUME1); -+ m_driverLib->TGPlayDevVoice(VOICE_VOLUME1); - - return true; - } -@@ -291,7 +284,7 @@ QByteArray FVSDDevice::acquireFeature() - void FVSDDevice::acquireFeatureStop() - { - m_driverLib->TGCancelGetImage(); -- if (m_futureWatcher != nullptr) -+ if (m_futureWatcher.data() != nullptr) - { - m_futureWatcher->waitForFinished(); - } -@@ -357,9 +350,7 @@ void FVSDDevice::enrollTemplateMerge() - { - QString id = QCryptographicHash::hash(mergedTemplate, QCryptographicHash::Md5).toHex(); - -- DeviceInfo deviceInfo; -- deviceInfo.idVendor = m_idVendor; -- deviceInfo.idProduct = m_idProduct; -+ DeviceInfo deviceInfo = this->deviceInfo(); - bool save = FeatureDB::getInstance()->addFeature(id, mergedTemplate, deviceInfo); - if (save) - { -@@ -396,9 +387,10 @@ QString FVSDDevice::identifyFeature(QByteArray feature, QStringList featureIDs) - { - QList saveList; - QString featureID; -+ DeviceInfo deviceInfo = this->deviceInfo(); - if (featureIDs.isEmpty()) - { -- saveList = FeatureDB::getInstance()->getFeatures(m_idVendor, m_idProduct); -+ saveList = FeatureDB::getInstance()->getFeatures(deviceInfo.idVendor, deviceInfo.idProduct); - } - else - { -@@ -481,7 +473,7 @@ BDriver *FVSDDevice::getDriver() - return nullptr; - } - --int FVSDDevice::needTemplatesCountForEnroll() -+int FVSDDevice::mergeTemplateCount() - { - return TEMPLATE_FV_NUM; - } -diff --git a/src/device/finger-vein/fv-sd-device.h b/src/device/finger-vein/fv-sd-device.h -index d1fa8c7..09b372b 100644 ---- a/src/device/finger-vein/fv-sd-device.h -+++ b/src/device/finger-vein/fv-sd-device.h -@@ -14,7 +14,8 @@ - - #pragma once - #include --#include "device/auth-device.h" -+#include "device/bio-device.h" -+#include - - namespace Kiran - { -@@ -25,7 +26,7 @@ enum ExtractFeatureMode - }; - - struct DriverLib; --class FVSDDevice : public AuthDevice -+class FVSDDevice : public BioDevice - { - Q_OBJECT - public: -@@ -53,13 +54,14 @@ private: - - QByteArray getFeatureFromImage(QByteArray image, ExtractFeatureMode mode); - -- int needTemplatesCountForEnroll() override; -+ int mergeTemplateCount() override; - int templateMatch(QByteArray fpTemplate1, QByteArray fpTemplate2) override; - void notifyEnrollProcess(EnrollProcess process, const QString &featureID = QString()) override; - void notifyIdentifyProcess(IdentifyProcess process, const QString &featureID = QString()) override; - - private: -- DriverLib *m_driverLib; -+ QSharedPointer m_driverLib; -+ - Handle m_libProcessHandle; - Handle m_libComHandle; - }; -diff --git a/src/device/fingerprint/fp-device.cpp b/src/device/fingerprint/fp-device.cpp -index d610844..cddaadd 100644 ---- a/src/device/fingerprint/fp-device.cpp -+++ b/src/device/fingerprint/fp-device.cpp -@@ -17,7 +17,7 @@ - namespace Kiran - { - FPDevice::FPDevice(QObject *parent) -- : AuthDevice{parent} -+ : BioDevice{parent} - { - } - -diff --git a/src/device/fingerprint/fp-device.h b/src/device/fingerprint/fp-device.h -index abbbcd1..7a792ba 100644 ---- a/src/device/fingerprint/fp-device.h -+++ b/src/device/fingerprint/fp-device.h -@@ -13,11 +13,11 @@ - */ - - #pragma once --#include "device/auth-device.h" -+#include "device/bio-device.h" - - namespace Kiran - { --class FPDevice : public AuthDevice -+class FPDevice : public BioDevice - { - public: - explicit FPDevice(QObject *parent = nullptr); -diff --git a/src/device/fingerprint/fp-zk-device.cpp b/src/device/fingerprint/fp-zk-device.cpp -index 1862446..8236787 100644 ---- a/src/device/fingerprint/fp-zk-device.cpp -+++ b/src/device/fingerprint/fp-zk-device.cpp -@@ -140,18 +140,15 @@ FPZKDevice::FPZKDevice(QObject* parent) - FPZKDevice::~FPZKDevice() - { - acquireFeatureStop(); -- m_futureWatcher->deleteLater(); - if (m_hDBCache) - { - m_driverLib->ZKFPM_DBFree(m_hDBCache); - m_hDBCache = NULL; - } - -- if (m_driverLib) -+ if (m_driverLib.data()) - { - m_driverLib->ZKFPM_Terminate(); // 释放资源 -- delete m_driverLib; -- m_driverLib = nullptr; - } - - if (m_libHandle) -@@ -193,7 +190,7 @@ bool FPZKDevice::loadLib() - return false; - } - -- m_driverLib = new DriverLib; -+ m_driverLib = QSharedPointer(new DriverLib); - m_driverLib->ZKFPM_Init = (T_ZKFPM_Init)dlsym(m_libHandle, "ZKFPM_Init"); - if (NULL == m_driverLib->ZKFPM_Init) - { -@@ -323,7 +320,7 @@ QByteArray FPZKDevice::acquireFeature() - void FPZKDevice::acquireFeatureStop() - { - m_doAcquire = false; -- if (m_futureWatcher != nullptr) -+ if (m_futureWatcher.data() != nullptr) - { - m_futureWatcher->waitForFinished(); - } -@@ -374,18 +371,15 @@ int FPZKDevice::templateMatch(QByteArray fpTemplate1, QByteArray fpTemplate2) - return score > 0 ? GENERAL_RESULT_OK : GENERAL_RESULT_FAIL; - } - --QString FPZKDevice::isFeatureEnrolled(QByteArray fpTemplate) --{ -- return identifyFeature(fpTemplate, QStringList()); --} - - QString FPZKDevice::identifyFeature(QByteArray fpTemplate, QStringList featureIDs) - { - QList saveList; - QString featureID; -+ DeviceInfo info = this->deviceInfo(); - if (featureIDs.isEmpty()) - { -- saveList = FeatureDB::getInstance()->getFeatures(m_idVendor, m_idProduct); -+ saveList = FeatureDB::getInstance()->getFeatures(info.idVendor, info.idProduct); - } - else - { -@@ -416,9 +410,7 @@ QString FPZKDevice::identifyFeature(QByteArray fpTemplate, QStringList featureID - - bool FPZKDevice::saveFPrintTemplate(QByteArray fpTemplate, const QString& featureID) - { -- DeviceInfo deviceInfo; -- deviceInfo.idVendor = m_idVendor; -- deviceInfo.idProduct = m_idProduct; -+ DeviceInfo deviceInfo = this->deviceInfo(); - bool save = FeatureDB::getInstance()->addFeature(featureID, fpTemplate, deviceInfo); - return save; - } -@@ -469,7 +461,7 @@ void FPZKDevice::enrollTemplateMerge() - internalStopEnroll(); - } - --int FPZKDevice::needTemplatesCountForEnroll() -+int FPZKDevice::mergeTemplateCount() - { - return FP_ZK_MEGER_TEMPLATE_COUNT; - } -diff --git a/src/device/fingerprint/fp-zk-device.h b/src/device/fingerprint/fp-zk-device.h -index 30e4e93..3379e2e 100644 ---- a/src/device/fingerprint/fp-zk-device.h -+++ b/src/device/fingerprint/fp-zk-device.h -@@ -14,6 +14,7 @@ - #pragma once - - #include -+#include - #include "auth-enum.h" - #include "fp-device.h" - -@@ -39,8 +40,8 @@ private: - void acquireFeatureStop() override; - void acquireFeatureFail() override; - void enrollTemplateMerge() override; -- int needTemplatesCountForEnroll() override; -- QString isFeatureEnrolled(QByteArray fpTemplate) override; -+ int mergeTemplateCount() override; -+ - void notifyEnrollProcess(EnrollProcess process, const QString& featureID = QString()) override; - void notifyIdentifyProcess(IdentifyProcess process, const QString& featureID = QString()) override; - -@@ -63,6 +64,6 @@ private: - private: - Handle m_libHandle; - Handle m_hDBCache; -- DriverLib* m_driverLib; -+ QSharedPointer m_driverLib; - }; - } // namespace Kiran -diff --git a/src/device/ukey/ukey-ft-device.cpp b/src/device/ukey/ukey-ft-device.cpp -new file mode 100644 -index 0000000..38017c6 ---- /dev/null -+++ b/src/device/ukey/ukey-ft-device.cpp -@@ -0,0 +1,400 @@ -+/** -+ * Copyright (c) 2020 ~ 2021 KylinSec Co., Ltd. -+ * kiran-biometrics is licensed under Mulan PSL v2. -+ * You can use this software according to the terms and conditions of the Mulan PSL v2. -+ * You may obtain a copy of Mulan PSL v2 at: -+ * http://license.coscl.org.cn/MulanPSL2 -+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, -+ * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, -+ * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. -+ * See the Mulan PSL v2 for more details. -+ * -+ * Author: luoqing -+ */ -+ -+#include "ukey-ft-device.h" -+#include -+#include -+#include -+#include "auth-enum.h" -+#include "auth_device_adaptor.h" -+#include "feature-db.h" -+ -+namespace Kiran -+{ -+#define FT_UKEY_DRIVER_LIB "libes_3000gm.so" -+#define UKEY_APP_NAME "KIRAN-AUTHENTICATION-DEVICES" -+#define UKEY_CONTAINER_NAME "1003-3001" -+ -+UKeyFTDevice::UKeyFTDevice(QObject *parent) : AuthDevice{parent}, -+ m_libHandle(nullptr), -+ m_appHandle(nullptr), -+ m_devHandle(nullptr), -+ m_containerHandle(nullptr) -+{ -+ setDeviceType(DEVICE_TYPE_UKey); -+ setDeviceDriver(FT_UKEY_DRIVER_LIB); -+} -+ -+UKeyFTDevice::~UKeyFTDevice() -+{ -+ m_driver->closeContainer(m_containerHandle); -+ m_driver->closeApplication(m_appHandle); -+ m_driver->disConnectDev(m_devHandle); -+ m_containerHandle = nullptr; -+ m_appHandle = nullptr; -+ m_devHandle = nullptr; -+} -+ -+bool UKeyFTDevice::initDevice() -+{ -+ m_driver = QSharedPointer(new UKeySKFDriver()); -+ if (!m_driver->loadLibrary(FT_UKEY_DRIVER_LIB)) -+ { -+ return false; -+ } -+ m_devHandle = m_driver->connectDev(); -+ if (!m_devHandle) -+ { -+ KLOG_DEBUG() << ""; -+ return false; -+ } -+ -+ return true; -+} -+ -+BDriver *UKeyFTDevice::getDriver() -+{ -+ return nullptr; -+} -+ -+void UKeyFTDevice::doingUKeyEnrollStart(const QString &pin, bool rebinding) -+{ -+ if (rebinding) -+ { -+ ULONG ulReval = m_driver->devAuth(m_devHandle); -+ if (ulReval == SAR_OK) -+ { -+ m_driver->deleteAllApplication(m_devHandle); -+ DeviceInfo deviceInfo = this->deviceInfo(); -+ QStringList idList = FeatureDB::getInstance()->getFeatureIDs(deviceInfo.idVendor, deviceInfo.idProduct); -+ Q_FOREACH (auto id, idList) -+ { -+ FeatureDB::getInstance()->deleteFeature(id); -+ } -+ bindingCurrentUser(); -+ } -+ else -+ { -+ KLOG_ERROR() << "rebinding failed"; -+ } -+ } -+ else -+ { -+ bindingCurrentUser(); -+ } -+ internalStopEnroll(); -+} -+ -+void UKeyFTDevice::bindingCurrentUser() -+{ -+ if (isExistPublicKey()) -+ { -+ notifyUKeyEnrollProcess(ENROLL_PROCESS_REPEATED_ENROLL); -+ return; -+ } -+ -+ ECCPUBLICKEYBLOB publicKey = genKeyPair(); -+ if (publicKey.BitLen != 0) -+ { -+ /** -+ * 存入PublicKey,生成FID,并返回FID,FID标识PublicKey -+ * 不用保存PublicKey和systemUser的关系,目前只有一个用户 -+ */ -+ QByteArray keyFeature; -+ QByteArray xCoordinateArray((char *)publicKey.XCoordinate, 64); -+ QByteArray yCoordinateArray((char *)publicKey.YCoordinate, 64); -+ keyFeature.append(publicKey.BitLen); -+ keyFeature.append(xCoordinateArray); -+ keyFeature.append(yCoordinateArray); -+ KLOG_DEBUG() << "keyFeature:" << keyFeature; -+ -+ QString featureID = QCryptographicHash::hash(keyFeature, QCryptographicHash::Md5).toHex(); -+ DeviceInfo deviceInfo = this->deviceInfo(); -+ -+ if (FeatureDB::getInstance()->addFeature(featureID, keyFeature, deviceInfo)) -+ { -+ notifyUKeyEnrollProcess(ENROLL_PROCESS_SUCCESS, SAR_OK, featureID); -+ } -+ else -+ { -+ KLOG_DEBUG() << "save feature fail"; -+ notifyUKeyEnrollProcess(ENROLL_PROCESS_SAVE_FAIL); -+ } -+ } -+ else -+ { -+ notifyUKeyEnrollProcess(ENROLL_PROCESS_SAVE_FAIL); -+ } -+} -+ -+bool UKeyFTDevice::isExistPublicKey() -+{ -+ DeviceInfo deviceInfo = this->deviceInfo(); -+ auto features = FeatureDB::getInstance()->getFeatures(deviceInfo.idVendor, deviceInfo.idProduct); -+ if (features.count() != 0) -+ { -+ return true; -+ } -+ else -+ { -+ return false; -+ } -+} -+ -+ECCPUBLICKEYBLOB UKeyFTDevice::genKeyPair() -+{ -+ ECCPUBLICKEYBLOB publicKey = {0}; -+ if (!isExistsApplication(UKEY_APP_NAME)) -+ { -+ // NOTE:必须通过设备认证后才能在设备内创建和删除应用 -+ ULONG ulReval = m_driver->devAuth(m_devHandle); -+ if (ulReval != SAR_OK) -+ { -+ KLOG_ERROR() << "Device auth failure: " << m_driver->getErrorReason(ulReval); -+ return publicKey; -+ } -+ else -+ { -+ KLOG_DEBUG() << "device auth success"; -+ } -+ m_driver->deleteAllApplication(m_devHandle); -+ KLOG_DEBUG() << "m_devHandle:" << m_devHandle; -+ KLOG_DEBUG() << "m_pin:" << m_pin; -+ m_appHandle = m_driver->createApplication(m_devHandle, m_pin, UKEY_APP_NAME); -+ m_containerHandle = m_driver->createContainer(m_appHandle, m_pin, UKEY_CONTAINER_NAME, &m_retryCount); -+ if (!m_appHandle || !m_containerHandle) -+ { -+ return publicKey; -+ } -+ } -+ -+ m_appHandle = m_driver->onOpenApplication(m_devHandle, (LPSTR)UKEY_APP_NAME); -+ m_containerHandle = m_driver->onOpenContainer(m_appHandle, m_pin, UKEY_CONTAINER_NAME, &m_retryCount); -+ if (!m_appHandle || !m_containerHandle) -+ { -+ return publicKey; -+ } -+ -+ ULONG ret = m_driver->genECCKeyPair(m_containerHandle, &publicKey); -+ if (ret == SAR_OK) -+ { -+ KLOG_DEBUG() << "gen ecc key pair success"; -+ } -+ else -+ { -+ KLOG_ERROR() << "gen ecc key pair failed:" << m_driver->getErrorReason(ret); -+ } -+ -+ return publicKey; -+} -+ -+bool UKeyFTDevice::isExistsApplication(const QString &appName) -+{ -+ QString appNames = m_driver->enumApplication(m_devHandle); -+ KLOG_DEBUG() << "enum app names:" << appNames; -+ if (appNames.contains(appName)) -+ { -+ return true; -+ } -+ return false; -+} -+ -+void UKeyFTDevice::doingUKeyIdentifyStart(const QString &pin) -+{ -+ QList saveList; -+ DeviceInfo deviceInfo = this->deviceInfo(); -+ if (m_identifyIDs.isEmpty()) -+ { -+ saveList = FeatureDB::getInstance()->getFeatures(deviceInfo.idVendor, deviceInfo.idProduct); -+ } -+ else -+ { -+ Q_FOREACH (auto id, m_identifyIDs) -+ { -+ QByteArray feature = FeatureDB::getInstance()->getFeature(id); -+ if (!feature.isEmpty()) -+ saveList << feature; -+ } -+ } -+ -+ if (saveList.count() != 0) -+ { -+ for (int j = 0; j < saveList.count(); j++) -+ { -+ auto saveTemplate = saveList.value(j); -+ identifyKeyFeature(saveTemplate); -+ } -+ } -+ else -+ { -+ KLOG_DEBUG() << "no found feature id"; -+ } -+ -+ internalStopIdentify(); -+} -+ -+void UKeyFTDevice::internalStopEnroll() -+{ -+ if (deviceStatus() == DEVICE_STATUS_DOING_ENROLL) -+ { -+ m_driver->closeContainer(m_containerHandle); -+ m_driver->closeApplication(m_appHandle); -+ acquireFeatureStop(); -+ m_enrollTemplates.clear(); -+ setDeviceStatus(DEVICE_STATUS_IDLE); -+ clearWatchedServices(); -+ KLOG_DEBUG() << "stop Enroll"; -+ } -+} -+ -+void UKeyFTDevice::internalStopIdentify() -+{ -+ if (deviceStatus() == DEVICE_STATUS_DOING_IDENTIFY) -+ { -+ m_driver->closeContainer(m_containerHandle); -+ m_driver->closeApplication(m_appHandle); -+ acquireFeatureStop(); -+ m_identifyIDs.clear(); -+ setDeviceStatus(DEVICE_STATUS_IDLE); -+ clearWatchedServices(); -+ KLOG_DEBUG() << "stopIdentify"; -+ } -+} -+ -+void UKeyFTDevice::identifyKeyFeature(QByteArray keyFeature) -+{ -+ m_appHandle = m_driver->onOpenApplication(m_devHandle, (LPSTR)UKEY_APP_NAME); -+ if (m_appHandle == nullptr) -+ { -+ notifyUKeyIdentifyProcess(IDENTIFY_PROCESS_NO_MATCH); -+ return; -+ } -+ -+ m_containerHandle = m_driver->onOpenContainer(m_appHandle, m_pin, UKEY_CONTAINER_NAME, &m_retryCount); -+ if (m_containerHandle == nullptr) -+ { -+ notifyUKeyIdentifyProcess(IDENTIFY_PROCESS_NO_MATCH); -+ return; -+ } -+ -+ ECCSIGNATUREBLOB Signature = {0}; -+ ULONG ret = m_driver->authSignData(m_containerHandle, m_devHandle, Signature); -+ if (ret != SAR_OK) -+ { -+ KLOG_DEBUG() << "auth sign data failed:" << m_driver->getErrorReason(ret); -+ notifyUKeyIdentifyProcess(IDENTIFY_PROCESS_NO_MATCH, ret); -+ return; -+ } -+ -+ ECCPUBLICKEYBLOB eccPubKey; -+ eccPubKey.BitLen = keyFeature.left(1).at(0); -+ auto xCoordinateArray = keyFeature.mid(1, sizeof(eccPubKey.XCoordinate)); -+ auto yCoordinateArray = keyFeature.mid(sizeof(eccPubKey.XCoordinate) + 1); -+ -+ memcpy(eccPubKey.XCoordinate, (unsigned char *)xCoordinateArray.data(), ECC_MAX_XCOORDINATE_BITS_LEN / 8); -+ memcpy(eccPubKey.YCoordinate, (unsigned char *)yCoordinateArray.data(), ECC_MAX_YCOORDINATE_BITS_LEN / 8); -+ -+ ret = m_driver->verifyData(m_devHandle, Signature, eccPubKey); -+ if (ret != SAR_OK) -+ { -+ KLOG_DEBUG() << "verify data failed:" << m_driver->getErrorReason(ret); -+ notifyUKeyIdentifyProcess(IDENTIFY_PROCESS_NO_MATCH, ret); -+ } -+ else -+ { -+ notifyUKeyIdentifyProcess(IDENTIFY_PROCESS_MACTCH); -+ } -+} -+ -+QByteArray UKeyFTDevice::acquireFeature() -+{ -+ return QByteArray(); -+} -+ -+void UKeyFTDevice::acquireFeatureStop() -+{ -+} -+void UKeyFTDevice::acquireFeatureFail() -+{ -+} -+ -+void UKeyFTDevice::notifyUKeyEnrollProcess(EnrollProcess process, ULONG error, const QString &featureID) -+{ -+ QString message; -+ switch (process) -+ { -+ case ENROLL_PROCESS_SUCCESS: -+ message = tr("Successed binding user"); -+ Q_EMIT m_dbusAdaptor->EnrollStatus(featureID, 100, ENROLL_RESULT_COMPLETE, message); -+ break; -+ case ENROLL_PROCESS_SAVE_FAIL: -+ message = tr("Binding user failed"); -+ Q_EMIT m_dbusAdaptor->EnrollStatus("", 0, ENROLL_RESULT_FAIL, message); -+ break; -+ case ENROLL_PROCESS_REPEATED_ENROLL: -+ message = tr("UKey has been bound"); -+ Q_EMIT m_dbusAdaptor->EnrollStatus("", 0, ENROLL_RESULT_UKEY_EXIST_BINDING, message); -+ default: -+ break; -+ } -+ if (!message.isEmpty()) -+ { -+ if (!featureID.isEmpty()) -+ { -+ KLOG_DEBUG() << QString("%1, feature id:%2").arg(message).arg(featureID); -+ } -+ else -+ { -+ KLOG_DEBUG() << message; -+ } -+ } -+} -+ -+void UKeyFTDevice::notifyUKeyIdentifyProcess(IdentifyProcess process, ULONG error, const QString &featureID) -+{ -+ QString message, reason; -+ if (error != SAR_OK) -+ { -+ reason = m_driver->getErrorReason(error); -+ KLOG_DEBUG() << "fail reason:" << reason; -+ } -+ -+ KLOG_DEBUG() << "m_retryCount:" << m_retryCount; -+ switch (process) -+ { -+ case IDENTIFY_PROCESS_NO_MATCH: -+ message = tr("identify fail!"); -+ // 目前只需要返回有关pin码的错误信息 -+ if (reason.contains("pin")) -+ { -+ message.append(reason); -+ } -+ message.append(QString(",remaining retry count: %1").arg(m_retryCount)); -+ Q_EMIT m_dbusAdaptor->IdentifyStatus("", IDENTIFY_RESULT_NOT_MATCH, message); -+ break; -+ case IDENTIFY_PROCESS_MACTCH: -+ message = tr("identify ukey success"); -+ Q_EMIT m_dbusAdaptor->IdentifyStatus(featureID, IDENTIFY_RESULT_MATCH, message); -+ break; -+ default: -+ break; -+ } -+ -+ if (!message.isEmpty()) -+ { -+ KLOG_DEBUG() << QString("%1, feature id:%2").arg(message).arg(featureID); -+ } -+} -+ -+} // namespace Kiran -diff --git a/src/device/ukey/ukey-ft-device.h b/src/device/ukey/ukey-ft-device.h -new file mode 100644 -index 0000000..901393b ---- /dev/null -+++ b/src/device/ukey/ukey-ft-device.h -@@ -0,0 +1,68 @@ -+/** -+ * Copyright (c) 2020 ~ 2021 KylinSec Co., Ltd. -+ * kiran-biometrics is licensed under Mulan PSL v2. -+ * You can use this software according to the terms and conditions of the Mulan PSL v2. -+ * You may obtain a copy of Mulan PSL v2 at: -+ * http://license.coscl.org.cn/MulanPSL2 -+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, -+ * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, -+ * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. -+ * See the Mulan PSL v2 for more details. -+ * -+ * Author: luoqing -+ */ -+ -+#pragma once -+#include -+#include "device/auth-device.h" -+#include "driver/ukey/ukey-skf-driver.h" -+#include "ukey-skf.h" -+#include -+ -+namespace Kiran -+{ -+struct DriverLib; -+ -+class UKeyFTDevice : public AuthDevice -+{ -+ Q_OBJECT -+public: -+ explicit UKeyFTDevice(QObject *parent = nullptr); -+ ~UKeyFTDevice(); -+ -+ bool initDevice() override; -+ BDriver *getDriver() override; -+ -+private: -+ void doingUKeyEnrollStart(const QString &pin, bool rebinding = false) override; -+ void doingUKeyIdentifyStart(const QString &pin) override; -+ -+ void internalStopEnroll() override; -+ void internalStopIdentify() override; -+ -+ void identifyKeyFeature(QByteArray keyFeature); -+ -+ bool isExistPublicKey(); -+ void bindingCurrentUser(); -+ ECCPUBLICKEYBLOB genKeyPair(); -+ -+ bool isExistsApplication(const QString &appName); -+ -+ void notifyUKeyEnrollProcess(EnrollProcess process, ULONG error = SAR_OK, const QString &featureID = QString()); -+ void notifyUKeyIdentifyProcess(IdentifyProcess process, ULONG error = SAR_OK, const QString &featureID = QString()); -+ -+ QByteArray acquireFeature() override; -+ void acquireFeatureStop() override; -+ void acquireFeatureFail() override; -+ -+private: -+ Handle m_libHandle; -+ DEVHANDLE m_devHandle; -+ HAPPLICATION m_appHandle; -+ HCONTAINER m_containerHandle; -+ ULONG m_retryCount = 1000000; -+ -+ QSharedPointer m_driver; -+}; -+ -+} // namespace Kiran -diff --git a/src/driver/ukey/ukey-skf-driver.cpp b/src/driver/ukey/ukey-skf-driver.cpp -new file mode 100644 -index 0000000..b81bda4 ---- /dev/null -+++ b/src/driver/ukey/ukey-skf-driver.cpp -@@ -0,0 +1,516 @@ -+/** -+ * Copyright (c) 2020 ~ 2021 KylinSec Co., Ltd. -+ * kiran-biometrics is licensed under Mulan PSL v2. -+ * You can use this software according to the terms and conditions of the Mulan PSL v2. -+ * You may obtain a copy of Mulan PSL v2 at: -+ * http://license.coscl.org.cn/MulanPSL2 -+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, -+ * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, -+ * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. -+ * See the Mulan PSL v2 for more details. -+ * -+ * Author: luoqing -+ */ -+#include "ukey-skf-driver.h" -+#include -+#include -+#include -+#include -+ -+namespace Kiran -+{ -+// 签名者ID -+#define CONF_DEFAULT_KEY_PUC_ID "PucId" -+#define CONF_DEFAULT_KEY_DEV_KEY "DevKey" -+ -+#define CONF_DEFAULT_KEY_ADMIN_PINCODE "AdminPinCode" -+#define CONF_DEFAULT_KEY_USER_PINCODE "UserPinCode" -+ -+#define SIGN_DATA "kiran-authentication-devices" -+ -+#define UKEY_DEFAULT_CONFIG "/etc/kiran-authentication-devices-sdk/ukey-skf.conf" -+#define ADMIN_PIN_RETRY_COUNT 10 -+#define USER_PIN_RETRY_COUNT 10 -+ -+extern "C" -+{ -+ typedef ULONG (*SKF_EnumDev_Func)(BOOL bPresent, LPSTR szNameList, ULONG *pulSize); -+ typedef ULONG (*SKF_ConnectDev_Func)(LPSTR szName, DEVHANDLE *phDev); -+ typedef ULONG (*SKF_DisConnectDev_Func)(DEVHANDLE hDev); -+ typedef ULONG (*SKF_GetDevState_Func)(LPSTR szDevName, ULONG *pulDevState); -+ typedef ULONG (*SKF_GetDevInfo_Func)(DEVHANDLE hDev, DEVINFO *pDevInfo); -+ typedef ULONG (*SKF_SetLabel_Func)(DEVHANDLE hDev, LPSTR szLabel); -+ typedef ULONG (*SKF_OpenApplication_Func)(DEVHANDLE hDev, LPSTR szAppName, HAPPLICATION *phApplication); -+ typedef ULONG (*SKF_VerifyPIN_Func)(HAPPLICATION hApplication, ULONG ulPINType, LPSTR szPIN, ULONG *pulRetryCount); -+ typedef ULONG (*SKF_OpenContainer_Func)(HAPPLICATION hApplication, LPSTR szContainerName, HCONTAINER *phContainer); -+ typedef ULONG (*SKF_GenECCKeyPair_Func)(HCONTAINER hContainer, ULONG ulAlgId, ECCPUBLICKEYBLOB *pBlob); -+ typedef ULONG (*SKF_CloseContainer_Func)(HCONTAINER hContainer); -+ typedef ULONG (*SKF_CloseApplication_Func)(HAPPLICATION hApplication); -+ typedef ULONG (*SKF_CreateApplication_Func)(DEVHANDLE hDev, LPSTR szAppName, LPSTR szAdminPin, -+ DWORD dwAdminPinRetryCount, LPSTR szUserPin, DWORD dwUserPinRetryCount, -+ DWORD dwCreateFileRights, HAPPLICATION *phApplication); -+ -+ typedef ULONG (*SKF_EnumApplication_Func)(DEVHANDLE hDev, LPSTR szAppName, ULONG *pulSize); -+ typedef ULONG (*SKF_ExportPublicKey_Func)(HCONTAINER hContainer, BOOL bSignFlag, BYTE *pbBlob, ULONG *pulBlobLen); -+ typedef ULONG (*SKF_Digest_Func)(HANDLE hHash, BYTE *pbData, ULONG ulDataLen, BYTE *pbHashData, ULONG *pulHashLen); -+ typedef ULONG (*SKF_DigestInit_Func)(DEVHANDLE hDev, ULONG ulAlgID, ECCPUBLICKEYBLOB *pPubKey, -+ unsigned char *pucID, ULONG ulIDLen, HANDLE *phHash); -+ typedef ULONG (*SKF_ECCSignData_Func)(HCONTAINER hContainer, BYTE *pbData, ULONG ulDataLen, PECCSIGNATUREBLOB pSignature); -+ typedef ULONG (*SKF_ECCVerify_Func)(DEVHANDLE hDev, ECCPUBLICKEYBLOB *pECCPubKeyBlob, BYTE *pbData, -+ ULONG ulDataLen, PECCSIGNATUREBLOB pSignature); -+ -+ typedef ULONG (*SKF_GenRandom_Func)(DEVHANDLE hDev, BYTE *pbRandom, ULONG ulRandomLen); -+ typedef ULONG (*SKF_SetSymmKey_Func)(DEVHANDLE hDev, BYTE *pbKey, ULONG ulAlgID, HANDLE *phKey); -+ typedef ULONG (*SKF_EncryptInit_Func)(HANDLE hKey, BLOCKCIPHERPARAM EncryptParam); -+ typedef ULONG (*SKF_Encrypt_Func)(HANDLE hKey, BYTE *pbData, ULONG ulDataLen, BYTE *pbEncryptedData, ULONG *pulEncryptedLen); -+ typedef ULONG (*SKF_DevAuth_Func)(DEVHANDLE hDev, BYTE *pbAuthData, ULONG ulLen); -+ typedef ULONG (*SKF_DeleteApplication_Func)(DEVHANDLE hDev, LPSTR szAppName); -+ typedef ULONG (*SKF_CreateContainer_Func)(HAPPLICATION hApplication, LPSTR szContainerName, HCONTAINER *phContainer); -+} -+ -+struct DriverLib -+{ -+ SKF_EnumDev_Func SKF_EnumDev; -+ SKF_ConnectDev_Func SKF_ConnectDev; -+ SKF_DisConnectDev_Func SKF_DisConnectDev; -+ SKF_GetDevState_Func SKF_GetDevState; -+ SKF_GetDevInfo_Func SKF_GetDevInfo; -+ SKF_SetLabel_Func SKF_SetLabel; -+ SKF_OpenApplication_Func SKF_OpenApplication; -+ SKF_VerifyPIN_Func SKF_VerifyPIN; -+ SKF_OpenContainer_Func SKF_OpenContainer; -+ SKF_GenECCKeyPair_Func SKF_GenECCKeyPair; -+ SKF_CloseContainer_Func SKF_CloseContainer; -+ SKF_CloseApplication_Func SKF_CloseApplication; -+ SKF_CreateApplication_Func SKF_CreateApplication; -+ SKF_EnumApplication_Func SKF_EnumApplication; -+ SKF_ExportPublicKey_Func SKF_ExportPublicKey; -+ SKF_Digest_Func SKF_Digest; -+ SKF_DigestInit_Func SKF_DigestInit; -+ SKF_ECCSignData_Func SKF_ECCSignData; -+ SKF_ECCVerify_Func SKF_ECCVerify; -+ SKF_GenRandom_Func SKF_GenRandom; -+ SKF_SetSymmKey_Func SKF_SetSymmKey; -+ SKF_EncryptInit_Func SKF_EncryptInit; -+ SKF_Encrypt_Func SKF_Encrypt; -+ SKF_DevAuth_Func SKF_DevAuth; -+ SKF_DeleteApplication_Func SKF_DeleteApplication; -+ SKF_CreateContainer_Func SKF_CreateContainer; -+}; -+ -+UKeySKFDriver::UKeySKFDriver(QObject *parent) : BDriver(parent) -+{ -+} -+ -+UKeySKFDriver::~UKeySKFDriver() -+{ -+ if (m_libHandle) -+ { -+ dlclose(m_libHandle); -+ m_libHandle = NULL; -+ } -+} -+ -+QString UKeySKFDriver::getName() -+{ -+ return QString(); -+} -+QString UKeySKFDriver::getFullName() -+{ -+ return QString(); -+} -+quint16 UKeySKFDriver::getDriverId() -+{ -+ return 0; -+} -+ -+bool UKeySKFDriver::loadLibrary(QString libPath) -+{ -+ if (!QFile::exists(UKEY_DEFAULT_CONFIG)) -+ { -+ return false; -+ } -+ -+ m_libHandle = dlopen(libPath.toStdString().c_str(), RTLD_NOW); -+ if (m_libHandle == nullptr) -+ { -+ KLOG_ERROR() << "Load ukey lib failed,error:" << dlerror(); -+ return false; -+ } -+ -+ m_driverLib = QSharedPointer(new DriverLib); -+ m_driverLib->SKF_EnumDev = (SKF_EnumDev_Func)dlsym(m_libHandle, "SKF_EnumDev"); -+ m_driverLib->SKF_ConnectDev = (SKF_ConnectDev_Func)dlsym(m_libHandle, "SKF_ConnectDev"); -+ m_driverLib->SKF_DisConnectDev = (SKF_DisConnectDev_Func)dlsym(m_libHandle, "SKF_DisConnectDev"); -+ m_driverLib->SKF_GetDevState = (SKF_GetDevState_Func)dlsym(m_libHandle, "SKF_GetDevState"); -+ m_driverLib->SKF_GetDevInfo = (SKF_GetDevInfo_Func)dlsym(m_libHandle, "SKF_GetDevInfo"); -+ m_driverLib->SKF_SetLabel = (SKF_SetLabel_Func)dlsym(m_libHandle, "SKF_SetLabel"); -+ m_driverLib->SKF_OpenApplication = (SKF_OpenApplication_Func)dlsym(m_libHandle, "SKF_OpenApplication"); -+ m_driverLib->SKF_VerifyPIN = (SKF_VerifyPIN_Func)dlsym(m_libHandle, "SKF_VerifyPIN"); -+ m_driverLib->SKF_OpenContainer = (SKF_OpenContainer_Func)dlsym(m_libHandle, "SKF_OpenContainer"); -+ m_driverLib->SKF_GenECCKeyPair = (SKF_GenECCKeyPair_Func)dlsym(m_libHandle, "SKF_GenECCKeyPair"); -+ m_driverLib->SKF_CloseContainer = (SKF_CloseContainer_Func)dlsym(m_libHandle, "SKF_CloseContainer"); -+ m_driverLib->SKF_CloseApplication = (SKF_CloseApplication_Func)dlsym(m_libHandle, "SKF_CloseApplication"); -+ m_driverLib->SKF_CreateApplication = (SKF_CreateApplication_Func)dlsym(m_libHandle, "SKF_CreateApplication"); -+ m_driverLib->SKF_EnumApplication = (SKF_EnumApplication_Func)dlsym(m_libHandle, "SKF_EnumApplication"); -+ m_driverLib->SKF_ExportPublicKey = (SKF_ExportPublicKey_Func)dlsym(m_libHandle, "SKF_ExportPublicKey"); -+ m_driverLib->SKF_Digest = (SKF_Digest_Func)dlsym(m_libHandle, "SKF_Digest"); -+ m_driverLib->SKF_DigestInit = (SKF_DigestInit_Func)dlsym(m_libHandle, "SKF_DigestInit"); -+ m_driverLib->SKF_ECCSignData = (SKF_ECCSignData_Func)dlsym(m_libHandle, "SKF_ECCSignData"); -+ m_driverLib->SKF_ECCVerify = (SKF_ECCVerify_Func)dlsym(m_libHandle, "SKF_ECCVerify"); -+ m_driverLib->SKF_GenRandom = (SKF_GenRandom_Func)dlsym(m_libHandle, "SKF_GenRandom"); -+ m_driverLib->SKF_SetSymmKey = (SKF_SetSymmKey_Func)dlsym(m_libHandle, "SKF_SetSymmKey"); -+ m_driverLib->SKF_EncryptInit = (SKF_EncryptInit_Func)dlsym(m_libHandle, "SKF_EncryptInit"); -+ m_driverLib->SKF_Encrypt = (SKF_Encrypt_Func)dlsym(m_libHandle, "SKF_Encrypt"); -+ m_driverLib->SKF_DevAuth = (SKF_DevAuth_Func)dlsym(m_libHandle, "SKF_DevAuth"); -+ m_driverLib->SKF_DeleteApplication = (SKF_DeleteApplication_Func)dlsym(m_libHandle, "SKF_DeleteApplication"); -+ m_driverLib->SKF_CreateContainer = (SKF_CreateContainer_Func)dlsym(m_libHandle, "SKF_CreateContainer"); -+ -+ return true; -+} -+ -+DEVHANDLE UKeySKFDriver::connectDev() -+{ -+ ULONG ulBufSize = 0; -+ ULONG ulReval = m_driverLib->SKF_EnumDev(TRUE, NULL, &ulBufSize); -+ -+ if (ulReval != SAR_OK) -+ { -+ KLOG_DEBUG() << "Enum Dev error:" << getErrorReason(ulReval); -+ return nullptr; -+ } -+ -+ LPSTR szNameList = (LPSTR)malloc(ulBufSize * sizeof(CHAR)); -+ memset(szNameList, '\0', ulBufSize); -+ ulReval = m_driverLib->SKF_EnumDev(TRUE, szNameList, &ulBufSize); -+ if (ulReval == SAR_OK) -+ { -+ LPSTR pszTemp = szNameList; -+ if (NULL == pszTemp) -+ { -+ KLOG_DEBUG() << "no found ukey device"; -+ return nullptr; -+ } -+ while (*pszTemp != '\0') -+ { -+ DEVHANDLE devHandle; -+ ulReval = m_driverLib->SKF_ConnectDev(pszTemp, &devHandle); -+ if (SAR_OK == ulReval) -+ { -+ return devHandle; -+ } -+ else -+ { -+ KLOG_ERROR() << "Connect Dev failed:" << getErrorReason(ulReval); -+ } -+ pszTemp += strlen((const char *)pszTemp) + 1; -+ } -+ } -+ free(szNameList); -+ return nullptr; -+} -+ -+void UKeySKFDriver::deleteAllApplication(DEVHANDLE devHandle) -+{ -+ ULONG ulReval = SAR_FAIL; -+ char szAppNames[256] = {0}; -+ ULONG ulSize = 256; -+ -+ ulReval = m_driverLib->SKF_EnumApplication(devHandle, (LPSTR)szAppNames, &ulSize); -+ if (SAR_OK == ulReval) -+ { -+ char *pszTemp = szAppNames; -+ if (*pszTemp == '\0') -+ { -+ m_driverLib->SKF_DeleteApplication(devHandle, (LPSTR)pszTemp); -+ } -+ -+ while (*pszTemp != '\0') -+ { -+ m_driverLib->SKF_DeleteApplication(devHandle, (LPSTR)pszTemp); -+ pszTemp += strlen((const char *)pszTemp) + 1; -+ } -+ } -+ KLOG_DEBUG() << "clear all application"; -+} -+ -+QString UKeySKFDriver::enumApplication(DEVHANDLE devHandle) -+{ -+ char szAppNames[256] = {0}; -+ ULONG ulSize = 256; -+ ULONG ret = m_driverLib->SKF_EnumApplication(devHandle, (LPSTR)szAppNames, &ulSize); -+ if (ret == SAR_OK) -+ { -+ QString appNames(szAppNames); -+ KLOG_DEBUG() << "enum app names:" << appNames; -+ return appNames; -+ } -+ else -+ { -+ return QString(); -+ } -+} -+ -+ULONG UKeySKFDriver::devAuth(DEVHANDLE devHandle) -+{ -+ BYTE random[16] = {0}; -+ BYTE devKey[16] = {0}; -+ BLOCKCIPHERPARAM param = {0}; -+ BYTE devkeyenc[16] = {0}; -+ ULONG dwResultLen = 16; -+ ULONG ulReval; -+ DEVINFO devInfo; -+ HANDLE hSessionKey; -+ -+ QString defaultDevKey = getDefaultValueFromConf(CONF_DEFAULT_KEY_DEV_KEY); -+ QByteArray byteArray = defaultDevKey.toLatin1(); -+ unsigned char *key = (unsigned char *)byteArray.data(); -+ -+ memcpy(devKey, key, 16); -+ -+ ulReval = m_driverLib->SKF_GenRandom(devHandle, random, 8); -+ if (SAR_OK != ulReval) -+ return ulReval; -+ -+ ulReval = m_driverLib->SKF_GetDevInfo(devHandle, &devInfo); -+ if (SAR_OK != ulReval) -+ return ulReval; -+ -+ ulReval = m_driverLib->SKF_SetSymmKey(devHandle, devKey, devInfo.DevAuthAlgId, &hSessionKey); -+ if (SAR_OK != ulReval) -+ return ulReval; -+ -+ ulReval = m_driverLib->SKF_EncryptInit(hSessionKey, param); -+ if (SAR_OK != ulReval) -+ return ulReval; -+ -+ ulReval = m_driverLib->SKF_Encrypt(hSessionKey, random, 16, devkeyenc, &dwResultLen); -+ if (SAR_OK != ulReval) -+ return ulReval; -+ -+ ulReval = m_driverLib->SKF_DevAuth(devHandle, devkeyenc, 16); -+ -+ return ulReval; -+} -+ -+HAPPLICATION UKeySKFDriver::onOpenApplication(DEVHANDLE devHandle, LPSTR szAppName) -+{ -+ HAPPLICATION phApplication = nullptr; -+ ULONG ret = m_driverLib->SKF_OpenApplication(devHandle, szAppName, &phApplication); -+ if (ret != SAR_OK) -+ { -+ KLOG_DEBUG() << "open Application failed:" << getErrorReason(ret); -+ return nullptr; -+ } -+ return phApplication; -+} -+ -+HCONTAINER UKeySKFDriver::onOpenContainer(HAPPLICATION appHandle, const QString &pin, QString containerName, ULONG *retryCount) -+{ -+ QByteArray byteArray = pin.toLatin1(); -+ unsigned char *szPIN = (unsigned char *)byteArray.data(); -+ ULONG ret = m_driverLib->SKF_VerifyPIN(appHandle, USER_TYPE, szPIN, retryCount); -+ if (ret == SAR_OK) -+ { -+ HCONTAINER containerHandle = nullptr; -+ ret = m_driverLib->SKF_OpenContainer(appHandle, (LPSTR)containerName.data(), &containerHandle); -+ if (ret == SAR_OK) -+ { -+ KLOG_DEBUG() << "open container success"; -+ return containerHandle; -+ } -+ else -+ { -+ KLOG_ERROR() << "open container failed:" << getErrorReason(ret); -+ } -+ } -+ else -+ { -+ KLOG_DEBUG() << "Verify PIN failed:" << getErrorReason(ret); -+ KLOG_DEBUG() << "Retry Count:" << retryCount; -+ } -+ return nullptr; -+} -+ -+void UKeySKFDriver::closeApplication(HAPPLICATION appHandle) -+{ -+ m_driverLib->SKF_CloseApplication(appHandle); -+} -+void UKeySKFDriver::closeContainer(HCONTAINER containerHandle) -+{ -+ m_driverLib->SKF_CloseContainer(containerHandle); -+} -+ -+void UKeySKFDriver::disConnectDev(DEVHANDLE devHandle) -+{ -+ m_driverLib->SKF_DisConnectDev(devHandle); -+} -+ -+HAPPLICATION UKeySKFDriver::createApplication(DEVHANDLE devHandle, QString pin, QString appName) -+{ -+ QByteArray pinArray = pin.toLatin1(); -+ unsigned char *userPin = (unsigned char *)pinArray.data(); -+ -+ QByteArray appNameArray = appName.toLatin1(); -+ unsigned char *szAppName = (unsigned char *)appNameArray.data(); -+ -+ QString defaultAdminPin = getDefaultValueFromConf(CONF_DEFAULT_KEY_ADMIN_PINCODE); -+ QByteArray byteArray = defaultAdminPin.toLatin1(); -+ unsigned char *adminPIn = (unsigned char *)byteArray.data(); -+ -+ HAPPLICATION appHandle = nullptr; -+ ULONG ulReval = m_driverLib->SKF_CreateApplication(devHandle, szAppName, adminPIn, -+ ADMIN_PIN_RETRY_COUNT, userPin, USER_PIN_RETRY_COUNT, -+ SECURE_USER_ACCOUNT, &appHandle); -+ ULONG retryCount; -+ if (ulReval != SAR_OK) -+ { -+ KLOG_ERROR() << "create application failed:" << getErrorReason(ulReval); -+ return nullptr; -+ } -+ -+ return appHandle; -+} -+ -+HCONTAINER UKeySKFDriver::createContainer(HAPPLICATION appHandle, QString pin, QString containerName, ULONG *retryCount) -+{ -+ QByteArray byteArray = pin.toLatin1(); -+ unsigned char *userPin = (unsigned char *)byteArray.data(); -+ ULONG ulReval = m_driverLib->SKF_VerifyPIN(appHandle, USER_TYPE, userPin, retryCount); -+ if (ulReval != SAR_OK) -+ { -+ KLOG_ERROR() << "verifyPin failed:" << getErrorReason(ulReval); -+ return nullptr; -+ } -+ -+ HCONTAINER containerHandle = nullptr; -+ ulReval = m_driverLib->SKF_CreateContainer(appHandle, (LPSTR)containerName.data(), &containerHandle); -+ if (ulReval != SAR_OK) -+ { -+ KLOG_ERROR() << "create container failed:" << getErrorReason(ulReval); -+ return nullptr; -+ } -+ KLOG_DEBUG() << "create new application and container success"; -+ return containerHandle; -+} -+ -+ULONG UKeySKFDriver::genECCKeyPair(HCONTAINER containerHandle, ECCPUBLICKEYBLOB *pBlob) -+{ -+ return m_driverLib->SKF_GenECCKeyPair(containerHandle, SGD_SM2_1, pBlob); -+} -+ -+ULONG UKeySKFDriver::authSignData(HCONTAINER containerHandle, DEVHANDLE devHandle, ECCSIGNATUREBLOB &Signature) -+{ -+ unsigned char *pPubKey = NULL; -+ ULONG ulPubKeyLen = 0; -+ ECCPUBLICKEYBLOB EccPubKey = {0}; -+ unsigned char pucId[32] = {0}; -+ ULONG ulIdLen = 16; -+ HANDLE hHash = nullptr; -+ ULONG ulHashLen = 64; -+ int signDataLen = strlen(SIGN_DATA); -+ unsigned char pbHashData[33] = {0}; -+ -+ QString defaultPucId = getDefaultValueFromConf(CONF_DEFAULT_KEY_PUC_ID); -+ QByteArray byteArray = defaultPucId.toLatin1(); -+ unsigned char *PUC_ID = (unsigned char *)byteArray.data(); -+ -+ ULONG ret = m_driverLib->SKF_ExportPublicKey(containerHandle, TRUE, pPubKey, &ulPubKeyLen); -+ if (ret != SAR_OK) -+ { -+ goto end; -+ } -+ -+ pPubKey = (unsigned char *)malloc(ulPubKeyLen); -+ if (pPubKey == NULL) -+ { -+ goto end; -+ } -+ -+ ret = m_driverLib->SKF_ExportPublicKey(containerHandle, TRUE, pPubKey, &ulPubKeyLen); -+ if (ret != SAR_OK) -+ { -+ goto end; -+ } -+ -+ if (ulPubKeyLen != sizeof(ECCPUBLICKEYBLOB)) -+ { -+ goto end; -+ } -+ -+ memcpy(&EccPubKey, pPubKey, ulPubKeyLen); -+ memcpy(pucId, PUC_ID, 16); -+ -+ ret = m_driverLib->SKF_DigestInit(devHandle, SGD_SM3, &EccPubKey, pucId, ulIdLen, &hHash); -+ if (ret != SAR_OK) -+ { -+ goto end; -+ } -+ -+ ret = m_driverLib->SKF_Digest(hHash, (BYTE *)SIGN_DATA, signDataLen, pbHashData, &ulHashLen); -+ if (ret != SAR_OK) -+ { -+ goto end; -+ } -+ -+ ret = m_driverLib->SKF_ECCSignData(containerHandle, pbHashData, ulHashLen, &Signature); -+ -+end: -+ getErrorReason(ret); -+ return ret; -+} -+ -+ULONG UKeySKFDriver::verifyData(DEVHANDLE devHandle, ECCSIGNATUREBLOB &Signature, ECCPUBLICKEYBLOB &publicKey) -+{ -+ unsigned char *pbInData = NULL, pbHashData[33] = {0}, pbOutData[256] = {0}; -+ ULONG ulInLen = 0, ulOutLen = 0, ulHashLen = 0, ulIdLen = 7; -+ unsigned char pucId[32] = {0}; -+ HANDLE hHash; -+ ULONG ulPubKeyLen = 0; -+ int signDataLen = strlen(SIGN_DATA); -+ QString defaultPucId = getDefaultValueFromConf(CONF_DEFAULT_KEY_PUC_ID); -+ QByteArray byteArray = defaultPucId.toLatin1(); -+ unsigned char *PUC_ID = (unsigned char *)byteArray.data(); -+ -+ memcpy(pucId, PUC_ID, 16); -+ ulIdLen = 16; -+ ULONG ulReval = m_driverLib->SKF_DigestInit(devHandle, SGD_SM3, &publicKey, pucId, ulIdLen, &hHash); -+ if (ulReval != SAR_OK) -+ { -+ goto end; -+ } -+ -+ ulHashLen = 64; -+ ulReval = m_driverLib->SKF_Digest(hHash, (BYTE *)SIGN_DATA, signDataLen, pbHashData, &ulHashLen); -+ if (ulReval != SAR_OK) -+ { -+ goto end; -+ } -+ -+ ulReval = m_driverLib->SKF_ECCVerify(devHandle, &publicKey, pbHashData, ulHashLen, &Signature); -+ -+end: -+ getErrorReason(ulReval); -+ return ulReval; -+} -+ -+QString UKeySKFDriver::getErrorReason(ULONG error) -+{ -+ for (int i = 0; i < sizeof(skf_errors) / sizeof(skf_errors[0]); i++) -+ { -+ if (error == skf_errors[i].err) -+ { -+ return skf_errors[i].reason; -+ } -+ } -+ return QString(); -+} -+ -+QString UKeySKFDriver::getDefaultValueFromConf(const QString &key) -+{ -+ QSettings confSettings(UKEY_DEFAULT_CONFIG, QSettings::NativeFormat); -+ QVariant value = confSettings.value(QString("default/%1").arg(key)); -+ return value.toString(); -+} -+ -+} // namespace Kiran -diff --git a/src/driver/ukey/ukey-skf-driver.h b/src/driver/ukey/ukey-skf-driver.h -new file mode 100644 -index 0000000..4952ff6 ---- /dev/null -+++ b/src/driver/ukey/ukey-skf-driver.h -@@ -0,0 +1,62 @@ -+/** -+ * Copyright (c) 2020 ~ 2021 KylinSec Co., Ltd. -+ * kiran-biometrics is licensed under Mulan PSL v2. -+ * You can use this software according to the terms and conditions of the Mulan PSL v2. -+ * You may obtain a copy of Mulan PSL v2 at: -+ * http://license.coscl.org.cn/MulanPSL2 -+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, -+ * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, -+ * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. -+ * See the Mulan PSL v2 for more details. -+ * -+ * Author: luoqing -+ */ -+#pragma once -+#include "driver/driver.h" -+#include "ukey-skf.h" -+#include -+ -+namespace Kiran -+{ -+struct DriverLib; -+ -+class UKeySKFDriver : public BDriver -+{ -+public: -+ UKeySKFDriver(QObject *parent = nullptr); -+ ~UKeySKFDriver(); -+ QString getName() override; -+ QString getFullName() override; -+ quint16 getDriverId() override; -+ -+ bool loadLibrary(QString libPath); -+ DEVHANDLE connectDev(); -+ void deleteAllApplication(DEVHANDLE devHandle); -+ QString enumApplication(DEVHANDLE devHandle); -+ -+ ULONG devAuth(DEVHANDLE devHandle); -+ HAPPLICATION onOpenApplication(DEVHANDLE hDev, LPSTR szAppName); -+ HCONTAINER onOpenContainer(HAPPLICATION appHandle,const QString &pin,QString containerName,ULONG *retryCount); -+ -+ void closeApplication(HAPPLICATION appHandle); -+ void closeContainer(HCONTAINER containerHandle); -+ void disConnectDev(DEVHANDLE devHandle); -+ -+ HAPPLICATION createApplication(DEVHANDLE devHandle,QString pin,QString appName); -+ HCONTAINER createContainer(HAPPLICATION appHandle, QString pin,QString containerName,ULONG *retryCount); -+ -+ ULONG genECCKeyPair(HCONTAINER containerHandle,ECCPUBLICKEYBLOB *pBlob); -+ -+ ULONG authSignData(HCONTAINER containerHandle,DEVHANDLE devHandle,ECCSIGNATUREBLOB &Signature); -+ ULONG verifyData(DEVHANDLE devHandle,ECCSIGNATUREBLOB &Signature, ECCPUBLICKEYBLOB &publicKey); -+ -+ QString getErrorReason(ULONG error); -+ -+ QString getDefaultValueFromConf(const QString &key); -+ -+private: -+ QSharedPointer m_driverLib; -+ HANDLE m_libHandle; -+}; -+ -+} // namespace Kiran -\ No newline at end of file -diff --git a/translations/kiran-authentication-devices.zh_CN.ts b/translations/kiran-authentication-devices.zh_CN.ts -index 40aef0a..540aadb 100644 ---- a/translations/kiran-authentication-devices.zh_CN.ts -+++ b/translations/kiran-authentication-devices.zh_CN.ts -@@ -4,63 +4,69 @@ - - Kiran::AuthDevice - -- -- -+ -+ - Device Busy - 设备忙 - - -- -+ - feature has reached the upper limit of %1 - 录入的特征已达上限 %1 - -+ -+ -+ -+ The pin code cannot be empty! -+ pin码不能为空! -+ - - - Kiran::FPZKDevice - -- -- -+ -+ - acquire fingerprint fail! - 获取指纹失败! - - -- -+ - Partial fingerprint feature entry - 录入部分指纹特征 - - -- -+ - The fingerprint has been enrolled - 指纹重复录入 - - -- -+ - Please place the same finger! - 请放置相同的手指! - - -- -- -+ -+ - Failed to enroll fingerprint, please enroll again - 录入指纹失败,请重新录入 - - -- -+ - Successed save finger - 录入指纹成功 - - -- -+ - Save Finger Failed! - 录入指纹失败! - - -- -+ - Fingerprint match - 指纹匹配 - - -- -+ - Fingerprint not match, place again - 指纹不匹配,请重试 - -@@ -68,52 +74,52 @@ - - Kiran::FVSDDevice - -- -+ - Finger vein image not obtained - 未获取到指静脉图像 - - -- -+ - Partial finger vein feature entry - 录入部分指静脉特征 - - -- -+ - The finger vein has been enrolled - 指静脉重复录入 - - -- -+ - Please place the same finger! - 请放置相同的手指! - - -- -+ - Finger vein template merged failed - 指静脉模板融合失败 - - -- -+ - Successed save feature - 录入特征成功 - - -- -+ - Save Feature Failed! - 录入特征失败! - - -- -+ - timeout, acquire finger vein fail! - 超时,获取指静脉失败! - - -- -+ - Feature Match - 特征匹配 - - -- -+ - Feature not match, place again - 特征不匹配,请重试 - -@@ -126,4 +132,32 @@ - 授权失败. - - -+ -+ Kiran::UKeyFTDevice -+ -+ -+ Successed binding user -+ 绑定用户成功 -+ -+ -+ -+ Binding user failed -+ 绑定用户失败 -+ -+ -+ -+ UKey has been bound -+ UKey已经被绑定 -+ -+ -+ -+ identify fail! -+ 认证失败! -+ -+ -+ -+ identify ukey success -+ ukey认证成功 -+ -+ - --- -2.33.0 - diff --git a/0002-fix-cmake-qt5-cmake-command-compatible.patch b/0002-fix-cmake-qt5-cmake-command-compatible.patch deleted file mode 100644 index 16d0c36..0000000 --- a/0002-fix-cmake-qt5-cmake-command-compatible.patch +++ /dev/null @@ -1,54 +0,0 @@ -From ef9a87e0379051c1c89b7d8b7955fba8c1d70f28 Mon Sep 17 00:00:00 2001 -From: wangyucheng -Date: Fri, 21 Apr 2023 10:25:10 +0800 -Subject: [PATCH] fix(cmake): qt5 cmake command compatible -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -- 兼容qt5 cmake命令的兼容修改 ---- - src/CMakeLists.txt | 8 ++++---- - 1 file changed, 4 insertions(+), 4 deletions(-) - -diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt -index cac6cb4..041f0dc 100644 ---- a/src/CMakeLists.txt -+++ b/src/CMakeLists.txt -@@ -18,7 +18,7 @@ file(GLOB_RECURSE SRC_CPP_FILES ./*.cpp) - - ADD_DEFINITIONS(-DQT_NO_KEYWORDS) - --qt_add_dbus_adaptor( -+qt5_add_dbus_adaptor( - AUTH_DEVICE_MANAGER_ADAPTOR_SRCS - ${CMAKE_SOURCE_DIR}/data/com.kylinsec.Kiran.AuthDevice.xml - ${CMAKE_SOURCE_DIR}/src/auth-device-manager.h -@@ -26,7 +26,7 @@ qt_add_dbus_adaptor( - auth_device_manager_adaptor - AuthDeviceManagerAdaptor) - -- qt_add_dbus_adaptor( -+ qt5_add_dbus_adaptor( - AUTH_DEVICE_ADAPTOR_SRCS - ${CMAKE_SOURCE_DIR}/data/com.kylinsec.Kiran.AuthDevice.Device.xml - ${CMAKE_SOURCE_DIR}/src/device/auth-device.h -@@ -41,7 +41,7 @@ set(CMAKE_INSTALL_RPATH ${DEVICE_SDK}/finger-vein/sd:${DEVICE_SDK}/fingerprint/z - - - set(TS_FILES "${PROJECT_SOURCE_DIR}/translations/${PROJECT_NAME}.zh_CN.ts") --qt_create_translation(QM_FILES -+qt5_create_translation(QM_FILES - ${CMAKE_CURRENT_SOURCE_DIR} - ${TS_FILES} - ) -@@ -80,4 +80,4 @@ install(TARGETS ${PROJECT_NAME} - - set(TRANSLATION_INSTALL_DIR ${CMAKE_INSTALL_FULL_DATADIR}/${PROJECT_NAME}/translations) - configure_file(config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h) --install(FILES ${QM_FILES} DESTINATION ${TRANSLATION_INSTALL_DIR}) -\ No newline at end of file -+install(FILES ${QM_FILES} DESTINATION ${TRANSLATION_INSTALL_DIR}) --- -2.33.0 - diff --git a/0003-fix-compile-fix-ld-error-undefined-reference-to-symb.patch b/0003-fix-compile-fix-ld-error-undefined-reference-to-symb.patch deleted file mode 100644 index f556277..0000000 --- a/0003-fix-compile-fix-ld-error-undefined-reference-to-symb.patch +++ /dev/null @@ -1,29 +0,0 @@ -From 1e33c89f9aabdc5362b8fbd1720cf45d117d888c Mon Sep 17 00:00:00 2001 -From: wangyucheng -Date: Fri, 21 Apr 2023 13:51:30 +0800 -Subject: [PATCH] fix(compile): fix ld error: undefined reference to symbol - 'dlclose@@GLIBC_2.2.5' -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -- 修复在低于2.34版本的glibc环境中出现的链接错误(从glibc2.34开始,将dl的实现放在了glibc中,所以在glibc2.34的环境中不需要链接dl,但是在低于2.34的环境还是需要链接) ---- - src/CMakeLists.txt | 1 + - 1 file changed, 1 insertion(+) - -diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt -index 041f0dc..c6c2001 100644 ---- a/src/CMakeLists.txt -+++ b/src/CMakeLists.txt -@@ -69,6 +69,7 @@ target_link_libraries(${PROJECT_NAME} - Qt5::DBus - Qt5::Sql - Qt5::Concurrent -+ ${CMAKE_DL_LIBS} - ) - - target_link_directories(${PROJECT_NAME} PRIVATE --- -2.33.0 - diff --git a/kiran-authentication-devices-2.5.0.tar.gz b/kiran-authentication-devices-2.5.0.tar.gz deleted file mode 100644 index b043da5bce29e8af40be2964084f1aa72e9caaaf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 36813 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 e4DNo3!y${Osux(3=UQU)z4RzZ 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

rpWa<<{Xn6K=aWYgAde+b8FlabY@mB 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 P0Fa(25WYaS;6VQee`=xm literal 0 HcmV?d00001 diff --git a/kiran-authentication-devices.spec b/kiran-authentication-devices.spec index ac3a55a..1530211 100644 --- a/kiran-authentication-devices.spec +++ b/kiran-authentication-devices.spec @@ -1,16 +1,12 @@ Name: kiran-authentication-devices -Version: 2.5.0 -Release: 5 +Version: 2.5.1 +Release: 1 Summary: Kiran Authentication Devices License: MulanPSL-2.0 Source0: %{name}-%{version}.tar.gz -Patch0001: 0001-feature-ukey-The-UKey-device-is-adapted.patch -Patch0002: 0002-fix-cmake-qt5-cmake-command-compatible.patch -Patch0003: 0003-fix-compile-fix-ld-error-undefined-reference-to-symb.patch - BuildRequires: cmake BuildRequires: gcc-c++ BuildRequires: qt5-qtbase-devel @@ -51,6 +47,7 @@ systemctl enable kiran-authentication-devices.service %files %{_bindir}/kiran-authentication-devices +%{_bindir}/kiran-ukey-manager %{_datadir}/kiran-authentication-devices/translations/*.qm %{_datadir}/dbus-1/system-services/com.kylinsec.Kiran.AuthDevice.service %{_sysconfdir}/dbus-1/system.d/com.kylinsec.Kiran.AuthDevice.Device.conf @@ -66,6 +63,11 @@ systemctl enable kiran-authentication-devices.service rm -rf ${buildroot} %changelog +* Wed May 24 2023 luoqing - 2.5.1-1 +- KYOS-F: add Ukey management tool. +- KYOS-F: added iristar device , supporting iris and facial enroll and identify. +- KYOS-F: fix some defects. + * Fri Apr 21 2023 wangyucheng - 2.5.0-5 - KYOS-F: fix ld error: undefined reference to symbol 'dlclose@@GLIBC_2.2.5'.