DEV Community

HarmonyOS
HarmonyOS

Posted on

Usage of Asset Store Kit

Read the original article:Usage of Asset Store Kit

Context

Asset Store Kit (ASSET for short) provides a series of open APIs to securely store and manage short sensitive data, including but not limited to passwords (accounts/passwords), tokens (application credentials), and important plaintext (such as bank card numbers).

Description

  • Encryption Algorithm: AES256-GCM is used for asset encryption and decryption.

  • Access Control Mechanisms:

    • Owner-based access control (Default): Assets can only be accessed by the service that created them. This mechanism also protects the integrity of the asset owner's identity.
    • Group-based access control: Applications from the same developer can be grouped, allowing them to share access to specific assets. Group IDs are configured during development and secured by digital signatures. An application can belong to multiple groups for fine-grained control.
    • Access control based on lock screen status:

    This provides different levels of security based on the device's unlock state:

    • DEVICE_POWERED_ON: Assets accessible after power-on.
    • DEVICE_FIRST_UNLOCKED (Default): Assets accessible after the first unlock post-power-on.
    • DEVICE_UNLOCKED: Assets accessible only when the device is unlocked.
    • Access control based on lock screen password setting: Assets are accessible only if a lock screen password has been set. This is disabled by default.
    • Access control based on user authentication: Assets are accessible only after successful user authentication (e.g., fingerprint, facial recognition, PIN). This is disabled by default and can have an authentication validity period of up to 10 minutes.

Let's see the adding and query steps.

Solution / Approach

Add an asset that is accessible when the user unlocks the device for the first time. The asset includes password demo_pwd, alias demo_alias, and additional information demo_label.

import { asset } from '@kit.AssetStoreKit';
import { util } from '@kit.ArkTS';
import { BusinessError } from '@kit.BasicServicesKit';
import { userAuth } from '@kit.UserAuthenticationKit';

function stringToArray(str: string): Uint8Array {
  let textEncoder = new util.TextEncoder();
  return textEncoder.encodeInto(str);
}

function addAsset() {
  let attr: asset.AssetMap = new Map();
  attr.set(asset.Tag.SECRET, stringToArray('demo_pwd'));
  attr.set(asset.Tag.ALIAS, stringToArray('demo_alias'));
  attr.set(asset.Tag.ACCESSIBILITY, asset.Accessibility.DEVICE_FIRST_UNLOCKED);
  attr.set(asset.Tag.DATA_LABEL_NORMAL_1, stringToArray('demo_label'));
  try {
    asset.add(attr).then(() => {
      console.info(`Asset added successfully.`);
    }).catch((err: BusinessError) => {
      console.error(`Failed to add Asset. Code is ${err.code}, message is ${err.message}`);
    })
  } catch (error) {
    let err = error as BusinessError;
    console.error(`Failed to add Asset. Code is ${err.code}, message is ${err.message}`);
  }
}

function arrayToString(arr: Uint8Array): string {
  let textDecoder = util.TextDecoder.create("utf-8", { ignoreBOM: true });
  let str = textDecoder.decodeToString(arr, { stream: false })
  return str;
}

async function userAuthenticate(challenge: Uint8Array): Promise<Uint8Array> {
  return new Promise((resolve, reject) => {
    const authParam: userAuth.AuthParam = {
      challenge: challenge,
      authType: [userAuth.UserAuthType.PIN],
      authTrustLevel: userAuth.AuthTrustLevel.ATL1,
    };
    const widgetParam: userAuth.WidgetParam = { title: ' Enter the lock screen password. ' };
    try {
      let userAuthInstance = userAuth.getUserAuthInstance(authParam, widgetParam);
      userAuthInstance.on('result', {
        onResult(result) {
          if (result.result == userAuth.UserAuthResultCode.SUCCESS) {
            console.info(`User identity authentication succeeded.`);
            resolve(result.token);
          } else {
            console.error(`User identity authentication failed.`);
            reject();
          }
        }
      });
      userAuthInstance.start();
    } catch (error) {
      let err = error as BusinessError;
      console.error(`User identity authentication failed. Code is ${err.code}, message is ${err.message}`);
      reject();
    }
  })
}

function preQueryAsset(): Promise<Uint8Array> {
  return new Promise((resolve, reject) => {
    try {
      let query: asset.AssetMap = new Map();
      query.set(asset.Tag.ALIAS, stringToArray('demo_alias'));
      asset.preQuery(query).then((challenge: Uint8Array) => {
        resolve(challenge);
      }).catch(() => {
        reject();
      })
    } catch (error) {
      let err = error as BusinessError;
      console.error(`Failed to pre-query Asset. Code is ${err.code}, message is ${err.message}`);
      reject();
    }
  });
}

async function postQueryAsset(challenge: Uint8Array) {
  let handle: asset.AssetMap = new Map();
  handle.set(asset.Tag.AUTH_CHALLENGE, challenge);
  try {
    await asset.postQuery(handle);
    console.info(`Succeeded in post-querying Asset.`);
  } catch (error) {
    let err = error as BusinessError;
    console.error(`Failed to post-query Asset. Code is ${err.code}, message is ${err.message}`);
  }
}

async function queryAsset() {
  // step1. Call asset.preQuery to obtain the challenge value.
  preQueryAsset().then(async (challenge: Uint8Array) => {
    try {
      // Step 2. Pass in the challenge value to start the user authentication dialog box.
      let authToken: Uint8Array = await userAuthenticate(challenge);
      // Step 3 After the user authentication is successful, pass in the challenge value and authorization token to query the plaintext of the asset.
      let query: asset.AssetMap = new Map();
      query.set(asset.Tag.ALIAS, stringToArray('demo_alias'));
      query.set(asset.Tag.RETURN_TYPE, asset.ReturnType.ALL);
      query.set(asset.Tag.AUTH_CHALLENGE, challenge);
      query.set(asset.Tag.AUTH_TOKEN, authToken);
      let res: Array<asset.AssetMap> = await asset.query(query);
      for (let i = 0; i < res.length; i++) {
        // parse the secret.
        let secret: Uint8Array = res[i].get(asset.Tag.SECRET) as Uint8Array;
        // parse uint8array to string
        let secretStr: string = arrayToString(secret);

        console.log(secretStr)
      }
      // Step 4. After the plaintext is obtained, call asset.postQuery to perform postprocessing.
      postQueryAsset(challenge);
    } catch (error) {
      // Step 5. If the operation after preQuery() fails, call asset.postQuery to perform postprocessing.
      postQueryAsset(challenge);
    }
  }).catch((err: BusinessError) => {
    console.error(`Failed to pre-query Asset. Code is ${err.code}, message is ${err.message}`);
  })
}

@Entry
@Component
struct Demo {
  build() {
    Column() {
      Button('Add').onClick(() => {
        addAsset()
      })
      Button('Query').onClick(() => {
        queryAsset()
      })
    }
    .justifyContent(FlexAlign.Center)
    .alignItems(HorizontalAlign.Center)
    .height('100%')
    .width('100%')
    .padding(12)
  }
}
Enter fullscreen mode Exit fullscreen mode

Key Takeaways

Asset store service (ASSET) can be used in the scenario, in which users need to view their bank card numbers using a financial/banking application. To ensure security, you can store bank card numbers in an asset store and enforce a user identity authentication before the back card number is accessed.

When a user wants to view the bank card number, the application requests an identity authentication (for example, the user needs to enter a lock screen password or pass certain biometric authentication). When the identity authentication is successful, the application obtains the bank card number from ASSET and presents it to the user.

0000000000011111111.20250825151201.00820836697611316232817180440747.png

Written by Mücahid Kincir

Top comments (0)