Ukey流程示例指导
开发模式选择
当前针对双向SSL认证场景,通常有以下两种开发模式。
浏览器使用系统ArkWeb能力并自定义客户端
-
端侧应用调用证书管理能力,拉起证书选择弹框,等待用户选择证书,获得keyUri作为resourceId。
-
端侧应用监听PIN认证回调,处理事件,调用证书管理能力拉起PIN认证弹窗。
-
此时证书管理会返回认证结果给端侧应用,端侧应用将结果返回给ArkWeb组件。
浏览器使用自定义Web组件并自定义客户端
-
端侧应用调用证书管理能力,拉起证书选择弹框,等待用户选择证书,将keyUri作为resourceId,透传到Web组件。
-
Web组件通过证书管理获取对应证书的数据,并且通过keyUri作为resourceId调用HUKS的打开资源接口,并查询PIN认证状态。
-
根据认证结果,进行处理:
- 如果未认证,需要让端侧应用拉起PIN认证,此时端侧应用收到认证请求并处理事件,调用证书管理能力拉起PIN认证弹窗,获得认证结果后返回给Web组件,接着执行已认证的流程。
- 如果已认证,Web组件可以直接调用HUKS的三段式接口initSession/updateSession/finishSession进行签名。
注意
如果要调用底层Extension的三段式能力,需要传入参数中必须包含OH_HUKS_TAG_KEY_CLASS,且值为OH_HUKS_KEY_CLASS_EXTENSION。
实践举例
以下是浏览器使用系统能力进行双向SSL认证开发举例。
-
端侧应用调用证书管理的能力,拉起证书选择弹框certificateManagerDialog.openAuthorizeDialog,等待用户选择证书。
import { certificateManagerDialog, certificateManager } from '@kit.DeviceCertificateKit'; import { BusinessError } from '@kit.BasicservicesKit'; import { common } from '@kit.AbilityKit'; import { UIContext } from '@kit.ArkUI'; /* context为应用的上下文信息,调用方自行获取,此处仅为示例 */ let context: common.Context = new UIContext().getHostContext() as common.Context; let certTypes: Array<certificateManagerDialog.CertificateType> = [ certificateManagerDialog.CertificateType.CREDENTIAL_USER, certificateManagerDialog.CertificateType.CREDENTIAL_APP, certificateManagerDialog.CertificateType.CREDENTIAL_UKEY ]; let certPurpose: certificateManager.CertificatePurpose = certificateManager.CertificatePurpose.PURPOSE_DEFAULT; let authorizeRequest: certificateManagerDialog.AuthorizeRequest = { certTypes: certTypes, certPurpose: certPurpose }; try { certificateManagerDialog.openAuthorizeDialog(context, authorizeRequest).then((certReference: certificateManagerDialog.CertReference) => { /* 需要记录选择证书弹窗中获取到的keyUri,方便后续使用 */ let keyUri = certReference.keyUri; console.info(`Succeeded in opening authorize dialog.`); }).catch((err: BusinessError) => { console.error(`Failed to open authorize dialog. Code: ${err.code}, message: ${err.message}`); }); } catch (err) { let error = err as BusinessError; console.error(`Failed to open authorize dialog. Code: ${error.code}, message: ${error.message}`); } -
Web组件解析证书,调用HUKS打开资源,并查询PIN认证状态。
首先调用证书管理的NDK接口解析用户选择的证书OH_CertManager_GetUkeyCertificate,获取对应的证书数据。
其次,根据用户选中的keyUri作为resourceId调用HUKS的NDK接口打开资源OH_Huks_OpenResource。
最后,调用HUKS的NDK接口查询PIN认证状态OH_Huks_GetUkeyPinAuthState。
#include "huks/native_huks_external_crypto_api.h" #include "huks/native_huks_param.h" #include <device_certificate/certmanager/cm_native_api.h> #include <device_certificate/certmanager/cm_native_type.h> #include "napi/native_api.h" #include <string.h> /* 1. 调用证书管理的NDK接口解析用户选择的证书,获取对应的证书数据 */ static napi_value GetUkeyCert(napi_env env, napi_callback_info info) { size_t argc = 1; napi_value argv[1] = { nullptr }; napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr); if (argc != 1) { return nullptr; } /* 获取keyUri长度 */ size_t length = 0; napi_get_value_string_utf8(env, argv[0], nullptr, 0, &length); /* 获取keyUri值 */ char *keyUriBuffer = static_cast<char*>(malloc(length + 1)); size_t result = 0; napi_get_value_string_utf8(env, argv[0], keyUriBuffer, length + 1, &result); OH_CM_Blob keyUri = { static_cast<uint32_t>(length + 1), (uint8_t*)keyUriBuffer }; /* 定义UkeyInfo入参 */ OH_CM_UkeyInfo ukeyInfo = { OH_CM_CERT_PURPOSE_SIGN }; /* USB凭据的属性信息,此处省略 */ /* 获取keyUri对应的Ukey证书详情 */ OH_CM_CredentialDetailList credentialList = { 0, nullptr }; int32_t ret = OH_CertManager_GetUkeyCertificate(&keyUri, &ukeyInfo, &credentialList); /* 调用结束释放内存 */ OH_CertManager_FreeUkeyCertificate(&credentialList); free(keyUriBuffer); /* 返回调用结果 */ napi_value retNapi; napi_create_int32(env, ret, &retNapi); return retNapi; } /* 2. 根据用户选中的keyUri作为resourceId,调用HUKS的NDK接口打开资源 */ OH_Huks_Result InitParamSet(struct OH_Huks_ExternalCryptoParamSet **paramSet, const struct OH_Huks_ExternalCryptoParam *params, uint32_t paramCount) { OH_Huks_Result ret = OH_Huks_InitExternalCryptoParamSet(paramSet); if (ret.errorCode != OH_HUKS_SUCCESS) { return ret; } ret = OH_Huks_AddExternalCryptoParams(*paramSet, params, paramCount); if (ret.errorCode != OH_HUKS_SUCCESS) { OH_Huks_FreeExternalCryptoParamSet(paramSet); return ret; } ret = OH_Huks_BuildExternalCryptoParamSet(paramSet); if (ret.errorCode != OH_HUKS_SUCCESS) { OH_Huks_FreeExternalCryptoParamSet(paramSet); return ret; } return ret; } static struct OH_Huks_ExternalCryptoParam g_openResourceParamsTest[] = {}; static napi_value OpenResource(napi_env env, napi_callback_info info) { size_t argc = 1; napi_value argv[1] = { nullptr }; napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr); if (argc != 1) { return nullptr; } /* 获取keyUri长度 */ size_t length = 0; napi_get_value_string_utf8(env, argv[0], nullptr, 0, &length); /* 获取keyUri值 */ char *keyUriBuffer = static_cast<char*>(malloc(length + 1)); size_t result = 0; napi_get_value_string_utf8(env, argv[0], keyUriBuffer, length + 1, &result); OH_Huks_Blob resourceId = {.size = static_cast<uint32_t>(length), .data = (uint8_t*)keyUriBuffer}; struct OH_Huks_ExternalCryptoParamSet *openResourceParamSet = nullptr; OH_Huks_Result ohResult; do { ohResult = InitParamSet(&openResourceParamSet, g_openResourceParamsTest, sizeof(g_openResourceParamsTest) / sizeof(OH_Huks_ExternalCryptoParam)); if (ohResult.errorCode != OH_HUKS_SUCCESS) { break; } ohResult = OH_Huks_OpenResource(&resourceId, openResourceParamSet); if (ohResult.errorCode != OH_HUKS_SUCCESS) { break; } } while (0); free(keyUriBuffer); OH_Huks_FreeExternalCryptoParamSet(&openResourceParamSet); OH_Huks_Result res = OH_Huks_OpenResource(&resourceId, openResourceParamSet); napi_value ret; napi_create_int32(env, res.errorCode, &ret); return ret; } /* 3. 调用HUKS的NDK接口查询PIN认证状态 */ static struct OH_Huks_ExternalCryptoParam g_getPinStateParamsTest[] = {}; static napi_value GetUkeyPinAuthState(napi_env env, napi_callback_info info) { size_t argc = 1; napi_value argv[1] = { nullptr }; napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr); if (argc != 1) { return nullptr; } /* 获取keyUri长度 */ size_t length = 0; napi_get_value_string_utf8(env, argv[0], nullptr, 0, &length); /* 获取keyUri值 */ char *keyUriBuffer = static_cast<char*>(malloc(length + 1)); size_t result = 0; napi_get_value_string_utf8(env, argv[0], keyUriBuffer, length + 1, &result); OH_Huks_Blob resourceId = {.size = static_cast<uint32_t>(length), .data = (uint8_t*)keyUriBuffer}; struct OH_Huks_ExternalCryptoParamSet *pinStateParamSet = nullptr; OH_Huks_ExternalPinAuthState authState = OH_HUKS_EXT_CRYPTO_PIN_NO_AUTH; OH_Huks_Result ohResult; do { ohResult = InitParamSet(&pinStateParamSet, g_getPinStateParamsTest, sizeof(g_getPinStateParamsTest) / sizeof(OH_Huks_ExternalCryptoParam)); if (ohResult.errorCode != OH_HUKS_SUCCESS) { break; } ohResult = OH_Huks_GetUkeyPinAuthState(&resourceId, pinStateParamSet, &authState); if (ohResult.errorCode != OH_HUKS_SUCCESS) { break; } } while (0); free(keyUriBuffer); OH_Huks_FreeExternalCryptoParamSet(&pinStateParamSet); napi_value ret; napi_create_int32(env, ohResult.errorCode, &ret); return ret; } /* 4. 接口定义与注册 */ static napi_value Init(napi_env env, napi_value exports) { napi_property_descriptor desc[] = { { "getUkeyCert", nullptr, GetUkeyCert, nullptr, nullptr, nullptr, napi_default, nullptr }, { "openResource", nullptr, OpenResource, nullptr, nullptr, nullptr, napi_default, nullptr }, { "getUkeyPinAuthState", nullptr, GetUkeyPinAuthState, nullptr, nullptr, nullptr, napi_default, nullptr }, }; napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc); return exports; } static napi_module demoModule = { .nm_version =1, .nm_flags = 0, .nm_filename = nullptr, .nm_register_func = Init, .nm_modname = "hello", .nm_priv = ((void*)0), .reserved = { 0 }, }; extern "C" __attribute__((constructor)) void RegisterHelloModule(void) { napi_module_register(&demoModule); }对应的d.ts接口声明如下:
export function getUkeyCert(keyUri: string): number; export function openResource(keyUri: string): number; export function getUkeyPinAuthState(keyUri: string): number;调用方式如下:
import testNapi from "libentry.so"; @Entry @Component struct Index { /* 通过证书弹窗接口获取 */ @State keyUri: string = 'Hello World'; build() { Row() { Column() { Text('Hello World') .fontSize(50) .fontWeight(FontWeight.Bold) .onClick(() => { testNapi.getUkeyCert(keyUri); testNapi.openResource(keyUri); testNapi.getUkeyPinAuthState(keyUri); }) } .width('100%') } .height('100%') } } -
根据认证结果进行处理。
如果未认证,需让端侧应用拉起PIN认证,此时端侧应用收到认证请求,处理事件,调用证书管理能力拉起PIN认证弹窗certificateManagerDialog.openUkeyAuthDialog,获得认证结果之后返回给Web组件,接着执行已认证的流程。
/* 拉起证书管理PIN认证弹窗 */ import { certificateManagerDialog } from '@kit.DeviceCertificateKit'; import { BusinessError } from '@kit.BasicServicesKit'; import { common } from '@kit.AbilityKit'; import { UIContext } from '@kit.ArkUI'; /* context为应用的上下文信息,调用方自行获取,此处仅为示例 */ let context: common.Context = new UIContext().getHostContext() as common.Context; /* keyUri为证书凭据的唯一标识符,在第一步中已记录了keyUri,此处仅为示例 */ let keyUri: string = "testKeyAlias"; let ukeyAuthRequest: certificateManagerDialog.UkeyAuthRequest = { keyUri: keyUri }; try { certificateManagerDialog.openUkeyAuthDialog(context, ukeyAuthRequest) .then(() => { console.info(`Succeeded in opening ukey authorization dialog.`); }).catch((err: BusinessError) => { console.error(`Failed to open ukey authorization dialog. Code: ${err.code}, message: ${err.message}`); }); } catch (err) { let error = err as BusinessError; console.error(`Failed to open ukey authorization dialog. Code: ${error.code}, message: ${error.message}`); }如果已认证,Web组件可以直接调用HUKS的三段式接口进行签名。
/* 调用huks三段式接口获取签名后的数据 */ #include "huks/native_huks_api.h" #include "huks/native_huks_param.h" #include "napi/native_api.h" #include <string.h> OH_Huks_Result InitParamSet(struct OH_Huks_ParamSet **paramSet, const struct OH_Huks_Param *params, uint32_t paramCount) { OH_Huks_Result ret = OH_Huks_InitParamSet(paramSet); if (ret.errorCode != OH_HUKS_SUCCESS) { return ret; } ret = OH_Huks_AddParams(*paramSet, params, paramCount); if (ret.errorCode != OH_HUKS_SUCCESS) { OH_Huks_FreeParamSet(paramSet); return ret; } ret = OH_Huks_BuildParamSet(paramSet); if (ret.errorCode != OH_HUKS_SUCCESS) { OH_Huks_FreeParamSet(paramSet); return ret; } return ret; } static struct OH_Huks_Param g_signParamsTest[] = { { .tag = OH_HUKS_TAG_ALGORITHM, .uint32Param = OH_HUKS_ALG_RSA }, { .tag = OH_HUKS_TAG_PURPOSE, .uint32Param = OH_HUKS_KEY_PURPOSE_SIGN }, { .tag = OH_HUKS_TAG_KEY_SIZE, .uint32Param = OH_HUKS_RSA_KEY_SIZE_2048 }, { .tag = OH_HUKS_TAG_PADDING, .uint32Param = OH_HUKS_PADDING_PSS }, { .tag = OH_HUKS_TAG_DIGEST, .uint32Param = OH_HUKS_DIGEST_SHA384 }, { .tag = OH_HUKS_TAG_KEY_CLASS, .uint32Param = OH_HUKS_KEY_CLASS_EXTENSION } }; static const uint32_t RSA_COMMON_SIZE = 1024; static const char *DATA_TO_SIGN = "testData"; // 此处的keyAlias可以参考第二步的resourceId获取方法,此处仅为示例 static const char *KEY_ALIAS = "testKeyAlias"; static napi_value Sign(napi_env env, napi_callback_info info) { // 假设g_keyAlias是获取的resourceId struct OH_Huks_Blob keyAlias = { (uint32_t)strlen(KEY_ALIAS), (uint8_t *)KEY_ALIAS }; struct OH_Huks_Blob inData = { (uint32_t)strlen(DATA_TO_SIGN), (uint8_t *)DATA_TO_SIGN }; struct OH_Huks_ParamSet *signParamSet = nullptr; OH_Huks_Result ohResult; do { ohResult = InitParamSet(&signParamSet, g_signParamsTest, sizeof(g_signParamsTest) / sizeof(OH_Huks_Param)); if (ohResult.errorCode != OH_HUKS_SUCCESS) { break; } // Init uint8_t handleS[sizeof(uint64_t)] = {0}; struct OH_Huks_Blob handleSign = { (uint32_t)sizeof(uint64_t), handleS }; ohResult = OH_Huks_InitSession(&keyAlias, signParamSet, &handleSign, nullptr); if (ohResult.errorCode != OH_HUKS_SUCCESS) { break; } // Finish uint8_t outDataS[RSA_COMMON_SIZE] = {0}; struct OH_Huks_Blob outDataSign = { RSA_COMMON_SIZE, outDataS }; ohResult = OH_Huks_FinishSession(&handleSign, signParamSet, &inData, &outDataSign); if (ohResult.errorCode != OH_HUKS_SUCCESS) { break; } } while (0); OH_Huks_FreeParamSet(&signParamSet); napi_value ret; napi_create_int32(env, ohResult.errorCode, &ret); return ret; }