HMAC使用用户身份认证控制访问
================

#### 问题现象

Universal Keystore Kit中HMAC(ArkTS)算法是否支持添加用户身份认证访问控制？

在加解密中加上用户身份认证可以正常运行，但是加到HMAC就报错，HMAC与用户身份认证的文档都没有明确说明支持还是不支持？

#### 背景知识

[HMAC(ArkTS)](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/huks-hmac-arkts)：密钥相关的哈希运算消息认证码（Hash-based Message Authentication Code）。

[用户身份认证访问控制开发指导](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/huks-user-identity-authentication)：生成/导入密钥时，可以指定密钥必须经过用户身份认证后才能使用。

#### 解决方案

使用HMAC生成密钥：

_// GenerateKey_
async function GenerateKey(authAccess: number, challengeType: number, time: number, isFineGrained: boolean) {
  console.info('\[HUKS>demo\] GenerateKey begin, challengeType:' + challengeType);
  let properties = GetPropertiesDebug(authAccess, challengeType, time, isFineGrained);
  let options: huks.HuksOptions = {
    properties: properties
  };


  let aliasTest: string;
  if (!isFineGrained) {
    aliasTest = alias;
  } else {
    aliasTest = alias2;
  }


  await huks.generateKeyItem(aliasTest, options).then((data) => {
    console.info('\[HUKS>demo\] GenerateKey Success! Result: ' + JSON.stringify(data));
  }).catch((err: BusinessError) => {
    console.error('\[HUKS>demo\] GenerateKey Exception: ' + JSON.stringify(err));
  });
}

调用用户身份认证访问密钥：

async function UserAuthBeforeMac(handle: number, options: huks.HuksOptions) {
  let authTypeList: number\[\] = new Array();


  authTypeList\[0\] = userIAM\_userAuth.UserAuthType.FINGERPRINT; _// 指纹认证类型_
  console.info('\[HUKS\] -> \[IAM\] auth Check START!!! userAuthType:\[' + authTypeList + '\] authTypeList: ' + authTypeList +
    '  challenge : ' + challengeData);
  const authParam: userIAM\_userAuth.AuthParam = {
    challenge: challengeData,
    authType: authTypeList,
    authTrustLevel: userIAM\_userAuth.AuthTrustLevel.ATL1
  };
  const widgetParam: userIAM\_userAuth.WidgetParam = {
    title: '请输入密码',
  };
  try {
    console.info('\[HUKS\] -> \[IAM\] auth start ...');
    let userAuthInstance = await userIAM\_userAuth.getUserAuthInstance(authParam, widgetParam);
    console.info('\[HUKS\] -> \[IAM\] get userAuth instance success');


    userAuthInstance.on('result', {
      onResult(result) {
        console.info('\[HUKS\] -> \[IAM\]  userAuthInstance callback result = ' + JSON.stringify(result));
        authTokenData = result.token;
        selfFinishSession(handle, options);
      }
    });
    userAuthInstance.start();
    console.info('\[HUKS\] -> \[IAM\] auth on success');
  } catch (error) {
    console.info('\[HUKS\] -> \[IAM\] auth catch error: ' + JSON.stringify(error));
  }
}

完整示例代码如下：

import huks from '@ohos.security.huks';
import userIAM\_userAuth from '@ohos.userIAM.userAuth';
import { BusinessError } from '@ohos.base';


let alias = 'test\_alias';
let alias2 = 'test\_alias2';
let accessIndex: number = 2;
let challengeTypeIndex: number = 0;
let handle: number = 0;
let challengeData: Uint8Array;
let authTokenData: Uint8Array;
let plainText = 'hello\_wxt';


function StringToUint8Array(str: string) {
  let arr: number\[\] = new Array();
  for (let i = 0, j = str.length; i < j; ++i) {
    arr.push(str.charCodeAt(i));
  }
  return new Uint8Array(arr);
}


function GetPropertiesDebug(authAccess: number, challengeType: number, time: number, isFineGrained: boolean) {
  console.info('\[HUKS>demo\] GetProperties start...');
  let properties: Array<huks.HuksParam> = new Array();
  let index: number = 0;
  properties\[index++\] = {
    tag: huks.HuksTag.HUKS\_TAG\_ALGORITHM,
    value: huks.HuksKeyAlg.HUKS\_ALG\_HMAC
  };
  properties\[index++\] = {
    tag: huks.HuksTag.HUKS\_TAG\_KEY\_SIZE,
    value: 256
  };
  properties\[index++\] = {
    tag: huks.HuksTag.HUKS\_TAG\_PURPOSE,
    value: huks.HuksKeyPurpose.HUKS\_KEY\_PURPOSE\_MAC
  };
  properties\[index++\] = {
    tag: huks.HuksTag.HUKS\_TAG\_DIGEST,
    value: huks.HuksKeyDigest.HUKS\_DIGEST\_SHA256
  };


  let huksAccessType: number = authAccess;
  let timeout: number = time;


  switch (authAccess) {
    case 1:
      huksAccessType = huks.HuksAuthAccessType.HUKS\_AUTH\_ACCESS\_INVALID\_CLEAR\_PASSWORD;
      break;
    case 2:
      huksAccessType = huks.HuksAuthAccessType.HUKS\_AUTH\_ACCESS\_INVALID\_NEW\_BIO\_ENROLL;
      break;
    case 3:
      huksAccessType = 4; _// alwaysValid_
      break;
    default:
      huksAccessType = 0;
  }
  properties\[index++\] = {
    tag: huks.HuksTag.HUKS\_TAG\_USER\_AUTH\_TYPE,
    value: huks.HuksUserAuthType.HUKS\_USER\_AUTH\_TYPE\_FINGERPRINT
  };
  properties\[index++\] = {
    tag: huks.HuksTag.HUKS\_TAG\_KEY\_AUTH\_ACCESS\_TYPE,
    value: huksAccessType
  };
  properties\[index++\] = {
    tag: huks.HuksTag.HUKS\_TAG\_CHALLENGE\_TYPE,
    value: challengeType
  };
  if (challengeType == huks.HuksChallengeType.HUKS\_CHALLENGE\_TYPE\_CUSTOM) {
    properties\[index++\] = {
      tag: huks.HuksTag.HUKS\_TAG\_CHALLENGE\_POS,
      value: huks.HuksChallengePosition.HUKS\_CHALLENGE\_POS\_1
    };
  }
  ;
  if (challengeType == huks.HuksChallengeType.HUKS\_CHALLENGE\_TYPE\_NONE) {
    properties\[index++\] = {
      tag: huks.HuksTag.HUKS\_TAG\_AUTH\_TIMEOUT,
      value: timeout
    };
  }
  ;
  if (isFineGrained) {
    properties\[index++\] = {
      tag: huks.HuksTag.HUKS\_TAG\_KEY\_AUTH\_PURPOSE,
      value: huks.HuksKeyPurpose.HUKS\_KEY\_PURPOSE\_VERIFY
    };
  }
  console.info('\[HUKS>demo\] GetProperties  finish : ');
  return properties;
}

_// GenerateKey_
async function GenerateKey(authAccess: number, challengeType: number, time: number, isFineGrained: boolean) {
  console.info('\[HUKS>demo\] GenerateKey begin, challengeType:' + challengeType);
  let properties = GetPropertiesDebug(authAccess, challengeType, time, isFineGrained);
  let options: huks.HuksOptions = {
    properties: properties
  };


  let aliasTest: string;
  if (!isFineGrained) {
    aliasTest = alias;
  } else {
    aliasTest = alias2;
  }


  await huks.generateKeyItem(aliasTest, options).then((data) => {
    console.info('\[HUKS>demo\] GenerateKey Success! Result: ' + JSON.stringify(data));
  }).catch((err: BusinessError) => {
    console.error('\[HUKS>demo\] GenerateKey Exception: ' + JSON.stringify(err));
  });
}

_// DeleteKey_
async function DeleteKey(isFineGrained: boolean) {
  console.info('\[HUKS>demo\] DeleteKey Begin');
  let emptyOption: huks.HuksOptions = {
    properties: \[\]
  };


  let aliasTest: string;
  if (!isFineGrained) {
    aliasTest = alias;
  } else {
    aliasTest = alias2;
  }


  await huks.deleteKeyItem(aliasTest, emptyOption).then((data) => {
    console.info('\[HUKS>demo\] DeleteKey Success! Result: ' + JSON.stringify(data));


  }).catch((err: BusinessError) => {
    console.info('\[HUKS>demo\] DeleteKey Exception: ' + JSON.stringify(err));
  });
}


async function UserAuthBeforeMac(handle: number, options: huks.HuksOptions) {
  let authTypeList: number\[\] = new Array();


  authTypeList\[0\] = userIAM\_userAuth.UserAuthType.FINGERPRINT; _// 指纹认证类型_
  console.info('\[HUKS\] -> \[IAM\] auth Check START!!! userAuthType:\[' + authTypeList + '\] authTypeList: ' + authTypeList +
    '  challenge : ' + challengeData);
  const authParam: userIAM\_userAuth.AuthParam = {
    challenge: challengeData,
    authType: authTypeList,
    authTrustLevel: userIAM\_userAuth.AuthTrustLevel.ATL1
  };
  const widgetParam: userIAM\_userAuth.WidgetParam = {
    title: '请输入密码',
  };
  try {
    console.info('\[HUKS\] -> \[IAM\] auth start ...');
    let userAuthInstance = await userIAM\_userAuth.getUserAuthInstance(authParam, widgetParam);
    console.info('\[HUKS\] -> \[IAM\] get userAuth instance success');


    userAuthInstance.on('result', {
      onResult(result) {
        console.info('\[HUKS\] -> \[IAM\]  userAuthInstance callback result = ' + JSON.stringify(result));
        authTokenData = result.token;
        selfFinishSession(handle, options);
      }
    });
    userAuthInstance.start();
    console.info('\[HUKS\] -> \[IAM\] auth on success');
  } catch (error) {
    console.info('\[HUKS\] -> \[IAM\] auth catch error: ' + JSON.stringify(error));
  }
}


async function selfFinishSession(handle: number, options: huks.HuksOptions) {
  console.info('\[HUKS>demo\] selfFinishSession Begin \[' + handle + '\]  #\*\*\*\*\* options: ' + JSON.stringify(options));
  await huks.finishSession(handle, options, authTokenData).then((data) => {
    console.info('\[HUKS>demo\] Sign() Sign Finish Success! Result: ' + JSON.stringify(data));
  }).catch((err: Error) => {
    console.error('\[HUKS>demo\] Sign() Sign Finish Exception: ' + JSON.stringify(err));
  });
}


async function Mac(challengeType: number) {
  console.info('\[HUKS>demo\] Mac Begin, challengeType: ' + challengeType);
  let properties: Array<huks.HuksParam> = new Array();
  let index = 0;
  properties\[index++\] = {
    tag: huks.HuksTag.HUKS\_TAG\_ALGORITHM,
    value: huks.HuksKeyAlg.HUKS\_ALG\_HMAC
  };
  properties\[index++\] = {
    tag: huks.HuksTag.HUKS\_TAG\_PURPOSE,
    value: huks.HuksKeyPurpose.HUKS\_KEY\_PURPOSE\_MAC
  };
  properties\[index++\] = {
    tag: huks.HuksTag.HUKS\_TAG\_KEY\_SIZE,
    value: huks.HuksKeySize.HUKS\_AES\_KEY\_SIZE\_256
  };
  properties\[index++\] = {
    tag: huks.HuksTag.HUKS\_TAG\_DIGEST,
    value: huks.HuksKeyDigest.HUKS\_DIGEST\_SHA256
  };
  if (challengeType == huks.HuksChallengeType.HUKS\_CHALLENGE\_TYPE\_CUSTOM) {
    properties\[index++\] = {
      tag: huks.HuksTag.HUKS\_TAG\_CHALLENGE\_POS,
      value: huks.HuksChallengePosition.HUKS\_CHALLENGE\_POS\_1
    };
  }


  let options: huks.HuksOptions = {
    properties: properties,
    inData: StringToUint8Array(plainText)
  };


  console.info('\[HUKS>demo\] Mac Init Begin');
  await huks.initSession(alias, options).then((data) => {
    handle = data.handle;
    challengeData = data.challenge as Uint8Array;
    console.info('\[HUKS>demo\] Mac() Mac Init Success! Result: ', JSON.stringify(data));
  }).catch((err: Error) => {
    console.error('\[HUKS>demo\] Mac() Mac Init Exception: ', JSON.stringify(err));
  });


  console.info('\[HUKS>demo\] Mac() Start to UserIAM, Auth Type: Finger');
  await UserAuthBeforeMac(handle, options);
}


@Entry
@Component
struct Index {
  @State message: string = 'Log: \\n';
  controller: TextInputController = new TextInputController();
  private authAccessType: string\[\] = \['None', 'CLEAR\_PASSWORD', 'NEW\_BIO\_ENROLL', 'AlwaysValid'\];
  private challengeType: string\[\] = \['Normal', 'Custom', 'None'\];
  private timeout: number = 60; // AuthToken有效期(s)


  build() {
    Column() {
      Row() {
       _ // AuthAccess类型_
        Text('AuthAccess类型:')
          .fontSize(20)
          .margin({ left: 10, top: 10 });
        Select(\[{ value: this.authAccessType\[0\] }, { value: this.authAccessType\[1\] }, { value: this.authAccessType\[2\] },
          { value: this.authAccessType\[3\] }\])
          .selected(0)
          .font({
            size: 20,
            weight: 400,
            family: 'serif',
            style: FontStyle.Normal
          })
          .selectedOptionFont({
            size: 30,
            weight: 500,
            family: 'serif',
            style: FontStyle.Normal
          })
          .optionFont({
            size: 20,
            weight: 400,
            family: 'serif',
            style: FontStyle.Normal
          })
          .onSelect((index: number, value: string) => {
            this.message += '您选择了AuthAccess类型-' + value + '\\n';
            accessIndex = index;
          })
          .margin({ top: 10 });
      };


     _ // challenge类型_
      Row() {
        Text('challenge类型:')
          .fontSize(20)
          .margin({ left: 10, top: 10 });
        Select(\[{ value: this.challengeType\[0\] }, { value: this.challengeType\[1\] }, { value: this.challengeType\[2\] }\])
          .selected(0)
          .font({
            size: 20,
            weight: 400,
            family: 'serif',
            style: FontStyle.Normal
          })
          .selectedOptionFont({
            size: 30,
            weight: 500,
            family: 'serif',
            style: FontStyle.Normal
          })
          .optionFont({
            size: 20,
            weight: 400,
            family: 'serif',
            style: FontStyle.Normal
          })
          .onSelect((index: number, value: string) => {
            this.message += '您选择了challenge类型-' + value + '\\n';
            challengeTypeIndex = index;
          })
          .margin({ top: 10 });
      };


      Row() {
        Button({ type: ButtonType.Normal, stateEffect: true }) {
          Text('generateKey')
            .fontSize(20)
            .fontColor(Color.White);
        }
        .borderRadius(8)
        .width('45%')
        .height('5%')
        .backgroundColor(0x317aff)
        .onClick(() => {
          this.message += 'generate key start\\n';
          GenerateKey(accessIndex, challengeTypeIndex, this.timeout, false); _// 生成密钥_
          this.message += 'generate key end\\n';
        })
        .margin(10);


        Button({ type: ButtonType.Normal, stateEffect: true }) {
          Text('deleteKey') _// 删除密钥_
            .fontSize(20)
            .fontColor(Color.White);
        }
        .borderRadius(8)
        .width('45%')
        .height('5%')
        .backgroundColor(0x317aff)
        .onClick(() => {
          this.message += 'delete key start\\n';
          DeleteKey(false);
          this.message += 'delete key end\\n';
        })
        .margin(10);
      };


      Row() {
        Button({ type: ButtonType.Normal, stateEffect: true }) {
          Text('Mac') _// 用户认证获取密钥_
            .fontSize(20)
            .fontColor(Color.White);
        }
        .borderRadius(8)
        .width('45%')
        .height('5%')
        .backgroundColor(0x317aff)
        .onClick(() => {
          this.message += 'mac data start\\n';
          Mac(challengeTypeIndex);
          this.message += 'mac data end\\n';
        })
        .margin(10);
      };


      Row() {
        Text(this.message)
          .fontSize(15)
          .width('100%')
          .margin(10);
      };
    }
    .alignItems(HorizontalAlign.Start);
  }
}