一个展览预约服务想知道用户是否具备某项入场资格,并不一定需要取得完整身份资料。数字身份接入可以从“验证一项声明”开始:用户选择凭证并同意披露,验证方判断声明是否可信、有效且针对本次请求。

API 26 SDK 的 DID 声明位于 OnlineAuthenticationKit,包含身份密钥与凭证操作。最小验证流程覆盖密钥、挑战签名和凭证接口;本地生成的标识符不代表可信身份,密钥受保护也不能证明凭证内容真实。

区分三个参与者

签发者负责声明,例如测试机构签发一张模拟预约资格凭证;持有者保管并决定是否出示;验证方检查签发者、声明内容和本次展示的有效性。

信任签发者需要明确依据。任何人都能写一段“具备资格”的文本,只有签名有效且签发者属于业务接受范围,声明才有意义。实验应使用独立测试身份,不收集真实身份证号码。

验证请求应尽量小

验证方发起请求时说明用途、接收方和所需声明,并附带一次性挑战及期限。以下结构仅用于业务层,系统凭证协议应按实际接口映射。

interface PresentationRequest {
  requestId: string;
  audience: string;
  purpose: string;
  requestedClaims: string[];
  challenge: string;
  expiresAt: number;
}

如果只需要资格状态,就不应默认索取姓名、生日与地址。具体凭证格式是否支持选择性披露,需要查阅对应协议;不支持时不能靠隐藏界面字段宣称密码学意义上的最小披露。

用户应能看见将向谁提供哪些信息,并能够拒绝。拒绝后提供其他适用的业务入口,避免反复弹出确认窗口。授权一次也不意味着以后任意场景都能复用同一份展示材料。

密钥保护与声明验证各司其职

TEE 级密钥保护关注私钥使用与隔离。验证方仍然需要检查签发者可信度、凭证签名、有效期和适用范围,并按凭证机制处理撤销状态。

不能将所有凭证都假定为同一种可解码令牌,也不能硬编码一个通用字段列表。接入前应固定支持的凭证格式和版本,使用官方或标准兼容实现完成验证。

对于无法获得撤销状态的情况,需要定义业务策略并向调用方返回明确结果。网络异常不是“未撤销”的证明。实验要把过期与撤销分成不同用例,便于定位验证逻辑。

一次展示只服务一次请求

展示结果必须绑定 audience 和 challenge。否则攻击者可能把为甲服务准备的材料转交给乙服务,或在有效期内重复使用。

验证服务保存挑战状态,并在成功接受时原子消费。并发重放测试应通过直接请求服务端执行,不能仅依赖客户端禁用按钮。服务器时钟和允许的时间偏差也需统一。

客户端页面关闭后,要释放暂存的凭证展示内容。调试日志使用请求标识与错误类别,不输出完整凭证。截图仅使用合成测试信息,不能为了文章效果暴露用户身份材料。

从可解释的失败开始验收

最小 Demo 包含测试签发者、持有者应用和验证端。先验证合法样本,再逐项覆盖未知签发者、过期、错误接收方、挑战不匹配、用户拒绝以及重复提交。

每个失败结果都应能解释属于哪一层:设备能力不可用、凭证选择取消、格式不支持、签名失败或业务条件不满足。把所有失败统一为“身份无效”,既不利于调试,也容易误导用户。

迁移设备或删除身份密钥以后,旧凭证还能否展示取决于绑定方式。此类恢复流程不应凭想象描述,需要使用实际接口和测试凭证验证后补入文章。

完整接入应交付什么

完整实验材料包括一组不含真实身份的数据样本、三方时序、验证日志和明确的失败矩阵。业务契约与请求状态机可以独立检查;设备密钥和凭证操作需在 API 26 环境与有效接入条件下执行。

凭证格式决定最小披露能做到哪一层

凭证能力可采用方式需要说明的边界
支持协议级选择性披露按协议只展示所需声明验证证明仍需完整可信链
不支持字段级披露选择包含更少数据的测试凭证隐藏 UI 字段不减少实际发送数据
验证方不接受格式返回格式不支持不转成普通 JSON 后继续声称可信

先选定测试发行方和格式,再实现 challenge 与 audience 验证,最后接持有者选择和设备密钥。恢复与迁移留作后续范围,第一版不承诺跨设备自动恢复。验收应同时检查发送数据与界面声明,确认用户看见的披露范围与实际一致。

参考:华为开发者能力介绍;API 26 SDK 的 DID 类型声明用于核对 Kit 归属和接口存在性。凭证格式、选择性披露和恢复机制应以实际采用的协议与接口为准。

密钥、签名与凭证对象的归属

密钥别名限定在 articlelab_ 前缀,避免实验复用其他业务密钥。身份导入明确禁止覆盖;查询后按 keyId 签署输入的挑战字节,不对 JSON 重新排版后再签名。

凭证导入与展示分别调用对应系统接口。导入把 isUpdate 固定为 false;展示要求用途和持有者配置。演示界面不打印凭证内容,验证方仍需检查签名、挑战绑定与撤销状态。

export class DidProbe {
  async createKey(context: common.Context, alias: string): Promise<did.GenerateKeyResponse> {
    if (!/^articlelab_[a-z0-9_]{1,40}$/.test(alias)) { throw new Error('use a dedicated articlelab_ alias'); }
    return did.generateKey(context, { keyAlias: alias,
      keyConfig: { algorithm: did.KeyAlgo.P256, purposeList: [did.KeyPurpose.SIGN, did.KeyPurpose.VERIFY] } });
  }
  async importIdentity(context: common.Context, identity: string, alias: string, keyId: string): Promise<void> {
    InputRules.required(identity, 'DID'); InputRules.required(keyId, 'keyId');
    if (!/^articlelab_[a-z0-9_]{1,40}$/.test(alias)) { throw new Error('unowned key alias'); }
    await did.importDid(context, { did: identity, isUpdate: false, didKeyList: [{ keyAlias: alias, keyId: keyId }] });
  }
  async query(context: common.Context, identity: string): Promise<did.QueryDidResponse> {
    return did.queryDid(context, { did: InputRules.required(identity, 'DID'),
      queryDidConfig: { requireDidKey: true, requireDidDoc: true } });
  }
  async signChallenge(context: common.Context, keyId: string, challengeHex: string): Promise<did.SignResponse> {
    return did.sign(context, { keyId: InputRules.required(keyId, 'keyId'), inData: InputRules.hex(challengeHex) });
  }
  async importCredential(context: common.Context, request: did.ImportDigitalCredentialRequest): Promise<did.ImportDigitalCredentialResponse> {
    InputRules.required(request.did, 'DID');
    InputRules.required(request.credentialData, 'credential', 65536);
    request.isUpdate = false;
    return did.importDigitalCredential(context, request);
  }
  async present(context: common.Context, request: did.GetDigitalCredentialRequest): Promise<did.GetDigitalCredentialResponse> {
    if (!request.displayConfig || !request.holderConfigList || request.holderConfigList.length === 0) {
      throw new Error('display purpose and holder configuration required');
    }
    InputRules.required(request.displayConfig.purpose, 'purpose');
    return did.getDigitalCredential(context, request);
  }
}

辅助校验与边界处理使用以下实现:

export class InputRules {
  static required(value: string, name: string, max: number = 256): string {
    if (typeof value !== 'string' || value.trim().length === 0 || value.length > max) {
      throw new Error(name + ' is missing or too long');
    }
    return value.trim();
  }
  static hex(value: string): Uint8Array {
    if (!/^[0-9a-fA-F]{64}$/.test(value)) { throw new Error('challenge must be 32 bytes of hex'); }
    const bytes: Uint8Array = new Uint8Array(32);
    for (let i: number = 0; i < bytes.length; i++) { bytes[i] = parseInt(value.substring(i * 2, i * 2 + 2), 16); }
    return bytes;
  }
}

用别名范围和挑战字节核对源码

合法 DID 文档与测试凭证、设备认证、签发者及验证方的真实链路。

检查层当前处理不能替代的结果
编译API 26 工程进行类型检查与打包目标设备支持
逻辑校验输入、状态或资源释放边界平台服务真实返回
联调按上面的输入和条件逐步执行不能以按钮文案代替核心结果

DID 密钥与数字凭证接口的版本约束,以 HarmonyOS 官方文档中心和当前 SDK 声明为准。

实验密钥范围与挑战字节

密钥创建限制在实验别名前缀,挑战经过长度和编码校验后再交给签名接口。测试只检查传参和拒绝路径,不把替身返回字节当成有效数字签名。宿主测试直接载入 DidProbe,仅以替身代替 DID Kit;load 与 test 属于测试框架。

test('DID: restrict creation to experiment alias and pass sign challenge bytes', async () => {
    let generated = 0;
    const C = load('services/DidProbe', {
        '@kit.AbilityKit': {}, '@kit.OnlineAuthenticationKit': {
            did: {
                KeyAlgo: {
                    P256: 3
                }, KeyPurpose: {
                    SIGN: 3, VERIFY: 4
                }, generateKey: async () => generated++, sign: async (c, r) => {
                    assert.equal(r.inData.length, 32);
                    return {
                        outData: new Uint8Array(4)
                    };
                }
            }
        }
    }).DidProbe;
    const x = new C();
    await assert.rejects(x.createKey({}, 'production'));
    assert.equal(generated, 0);
    await x.createKey({}, 'articlelab_sample');
    assert.equal(generated, 1);
    assert.equal((await x.signChallenge({}, 'key', 'ab'.repeat(32))).outData.length, 4);
});
操作或边界应检查的结果
输入不满足前提不调用后续平台操作
平台拒绝或抛错保留错误,不生成成功状态
再次进入或重试检查旧状态不会污染新任务
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐