• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2025 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import Result from '../../common/Result'
17import { ResultMsg } from '../../common/ResultMsg'
18import Constants from '../../common/constant';
19import { getFileFd, getAppIdWithUserId, getOsAccountInfo, getCurrentTime } from '../../common/FileUtils/utils';
20import { HiLog } from '../../common/HiLog';
21import { dlpPermission } from '@kit.DataProtectionKit';
22import DecryptContent from '../data/DecryptContent';
23import { hiTraceMeter } from '@kit.PerformanceAnalysisKit';
24import { DecryptState, DecryptStatus, OpenDlpFileManager } from '../manager/OpenDlpFileManager';
25import fs from '@ohos.file.fs';
26import { BusinessError } from '@kit.BasicServicesKit';
27import AccountManager from '../../manager/AccountManager';
28import { common, uriPermissionManager, wantConstant } from '@kit.AbilityKit';
29import { closeDlpFile } from '../common/DataUtils/DataUtils';
30import OpeningDialogManager from '../manager/OpeningDialogManager';
31import ApplyEfficiencyManager from '../manager/ApplyEfficiencyManager';
32import { ObjectUtil } from '../../common/ObjectUtil';
33import GlobalContext from '../../common/GlobalContext';
34import { FileParseType } from '../../bean/data/FileParseType';
35
36const TAG: string = 'DecryptHandler';
37
38interface TimeoutData {
39  timeout10: number | undefined;
40  timeoutPromise: Promise<never>;
41}
42
43export default class DecryptHandler {
44  constructor() {
45  }
46
47  public async getDecryptData(decryptContent: DecryptContent, context: common.ServiceExtensionContext):
48    Promise<Result<void>> {
49    // 1. Check if decrypted
50    const statusRet = await this.getDecryptState(decryptContent);
51    if (statusRet.errcode !== Constants.ERR_CODE_SUCCESS) {
52      HiLog.error(TAG, 'getDecryptState error');
53      return ResultMsg.buildMsg(statusRet.errcode, statusRet.errmsg);
54    }
55    const hasDecrypted = statusRet.result;
56    HiLog.info(TAG, `hasDecrypted: ${hasDecrypted}`);
57
58    // 2. If not decrypted, decryptData
59    if (!hasDecrypted) {
60      const decRet = await this.decryptDataAndSetStatus(decryptContent);
61      if (decRet.errcode !== Constants.ERR_CODE_SUCCESS) {
62        HiLog.error(TAG, 'decryptDataAndSetStatus error');
63        return ResultMsg.buildMsg(decRet.errcode, decRet.errmsg);
64      }
65    }
66
67    // 3. Install dlp sandbox and get sandbox information
68    hiTraceMeter.startTrace('DlpInstallSandboxJs', decryptContent.openDlpFileData.startId);
69    const getAppInfoRet = await this.getAppInfo(decryptContent);
70    hiTraceMeter.finishTrace('DlpInstallSandboxJs', decryptContent.openDlpFileData.startId);
71    if (getAppInfoRet.errcode !== Constants.ERR_CODE_SUCCESS || !getAppInfoRet.result) {
72      HiLog.error(TAG, 'getAppInfo error');
73      return ResultMsg.buildMsg(getAppInfoRet.errcode, getAppInfoRet.errmsg);
74    }
75    const appInfo = getAppInfoRet.result;
76
77    // 4. If has decrypted, check appInfo consistent
78    if (hasDecrypted) {
79      HiLog.info(TAG, 'getDecryptData hasDecrypted');
80      const originalAppInfo = decryptContent.appInfo;
81      if (originalAppInfo.appIndex !== appInfo.appIndex || originalAppInfo.tokenID !== appInfo.tokenID) {
82        this.deleteDecryptData(decryptContent);
83        const decRet = await this.decryptDataAndSetStatus(decryptContent);
84        if (decRet.errcode !== Constants.ERR_CODE_SUCCESS) {
85          HiLog.error(TAG, 'decryptDataAndSetStatus failed');
86          return ResultMsg.buildMsg(decRet.errcode, decRet.errmsg);
87        }
88      }
89    }
90    decryptContent.appInfo = appInfo;
91
92    // 5. Generate link info
93    if (!hasDecrypted) {
94      const getLinkInfoRet = await this.getLinkInfo(decryptContent);
95      if (getLinkInfoRet.errcode !== Constants.ERR_CODE_SUCCESS) {
96        HiLog.error(TAG, 'getLinkInfo error');
97        return ResultMsg.buildMsg(getLinkInfoRet.errcode, getLinkInfoRet.errmsg);
98      }
99    }
100
101    // 6. setNeedStartAbility
102    OpeningDialogManager.getInstance().setNeedStartAbility(decryptContent.openDlpFileData.requestId);
103    return ResultMsg.buildSuccess();
104  }
105
106  private async getDecryptState(decryptContent: DecryptContent): Promise<Result<boolean>> {
107    HiLog.info(TAG, 'start getDecryptState');
108    const requestId = decryptContent.openDlpFileData.requestId;
109    const needShowToast = decryptContent.openDlpFileData.needShowToast;
110    const statusRet = this.checkZipCancelByUser(decryptContent);
111    if (statusRet.errcode !== Constants.ERR_CODE_SUCCESS || !statusRet.result) {
112      HiLog.error(TAG, 'createDecryptTask checkZipCancelByUser failed');
113      return ResultMsg.buildMsg(statusRet.errcode, statusRet.errmsg);
114    }
115    const manager = OpenDlpFileManager.getInstance();
116    HiLog.debug(TAG, `status ${statusRet.result.state}`);
117
118    // Get has decrypted decryptContent
119    let hasDecrypted = (statusRet.result.state === DecryptState.DECRYPTED);
120    if (hasDecrypted) {
121      let getContentRet = manager.getHasDecryptedContent(decryptContent.openDlpFileData.uri);
122      if (getContentRet.errcode !== Constants.ERR_CODE_SUCCESS || !getContentRet.result) {
123        HiLog.error(TAG, 'getHasDecryptedContent error');
124        return ResultMsg.buildMsg(getContentRet.errcode, getContentRet.errmsg);
125      }
126      const decrypt = getContentRet.result;
127      if (decrypt.openDlpFileData.sandboxBundleName !== decryptContent.openDlpFileData.sandboxBundleName) {
128        HiLog.error(TAG, 'other app is opening this file');
129        return ResultMsg.getErrMsg(Constants.ERR_JS_OTHER_APP_OPEN_FILE);
130      }
131      const checkIsSameUser = await this.checkDistributedInfoId(decrypt);
132      if (!checkIsSameUser) {
133        HiLog.info(TAG, 'checkDistributedInfoId not same');
134        hasDecrypted = false;
135      } else {
136        HiLog.info(TAG, 'this file has decrypted');
137        ObjectUtil.Assign(decryptContent, decrypt);
138        decryptContent.hasDecrypted = true;
139        decryptContent.openDlpFileData.requestId = requestId;
140        decryptContent.openDlpFileData.needShowToast = needShowToast;
141        await OpeningDialogManager.getInstance().hideOpeningDialog(requestId);
142      }
143    }
144    return ResultMsg.buildSuccess(hasDecrypted);
145  }
146
147  private async decryptDataAndSetStatus(decryptContent: DecryptContent): Promise<Result<void>> {
148    HiLog.info(TAG, 'start decryptDataAndSetStatus');
149    const manager = OpenDlpFileManager.getInstance();
150    const setStatusRet = await manager.setStatus(decryptContent.openDlpFileData.uri,
151      { state: DecryptState.DECRYPTING, startTime: getCurrentTime() });
152    if (setStatusRet.errcode !== Constants.ERR_CODE_SUCCESS) {
153      HiLog.error(TAG, 'setStatus DECRYPTING failed');
154      return ResultMsg.buildMsg(setStatusRet.errcode, setStatusRet.errmsg);
155    }
156
157    hiTraceMeter.startTrace('DlpOpenDlpFileJs', decryptContent.openDlpFileData.startId);
158    const decRet = await this.decryptData(decryptContent);
159    hiTraceMeter.finishTrace('DlpOpenDlpFileJs', decryptContent.openDlpFileData.startId);
160    if (decRet.errcode !== Constants.ERR_CODE_SUCCESS) {
161      HiLog.error(TAG, 'decryptDataAndSetStatus decryptData error');
162      const deleteStatusRet = await manager.deleteStatus(decryptContent.openDlpFileData.uri);
163      if (deleteStatusRet.errcode !== Constants.ERR_CODE_SUCCESS) {
164        HiLog.error(TAG, 'deleteStatus failed');
165        return ResultMsg.buildMsg(deleteStatusRet.errcode, deleteStatusRet.errmsg);
166      }
167      return ResultMsg.buildMsg(decRet.errcode, decRet.errmsg);
168    }
169    return ResultMsg.buildSuccess();
170  }
171
172  private async decryptData(decryptContent: DecryptContent): Promise<Result<void>> {
173    HiLog.info(TAG, 'begin decryptData');
174    const uri = decryptContent.openDlpFileData.uri;
175    let getFileFdRet = getFileFd(uri, fs.OpenMode.READ_WRITE);
176    if (getFileFdRet.errcode !== Constants.ERR_CODE_SUCCESS || !getFileFdRet.result) {
177      HiLog.info(TAG, 'getFileFd with READ_WRITE failed, try to READ_ONLY.');
178      getFileFdRet = getFileFd(uri, fs.OpenMode.READ_ONLY);
179      if (getFileFdRet.errcode !== Constants.ERR_CODE_SUCCESS || !getFileFdRet.result) {
180        HiLog.error(TAG, 'getFileFd with READ_ONLY error.');
181        return ResultMsg.buildMsg(getFileFdRet.errcode, getFileFdRet.errmsg);
182      }
183    }
184    decryptContent.dlpFd = getFileFdRet.result;
185
186    HiLog.info(TAG, `decryptData: ${decryptContent.fileName}, dlpFd: ${decryptContent.dlpFd}`);
187    const sandboxBundleName = decryptContent.openDlpFileData.sandboxBundleName;
188    const userId = decryptContent.userId;
189    let getAppIdRet = await getAppIdWithUserId(sandboxBundleName, userId);
190    if (getAppIdRet.errcode !== Constants.ERR_CODE_SUCCESS || !getAppIdRet.result) {
191      HiLog.error(TAG, 'getAppIdWithUserId error');
192      return ResultMsg.buildMsg(getAppIdRet.errcode, getAppIdRet.errmsg);
193    }
194    let callerAppId = getAppIdRet.result;
195
196    let dlpFileRet = await this.createDecryptTask(callerAppId, decryptContent);
197    if (dlpFileRet.errcode !== Constants.ERR_CODE_SUCCESS || !dlpFileRet.result) {
198      HiLog.error(TAG, 'createDecryptTask error');
199      return ResultMsg.buildMsg(dlpFileRet.errcode, dlpFileRet.errmsg);
200    }
201    HiLog.info(TAG, 'createDecryptTask success');
202
203    let dlpFile = dlpFileRet.result;
204    decryptContent.dlpFile = dlpFileRet.result;
205    decryptContent.fileMetaInfo.accountType = dlpFile.dlpProperty.ownerAccountType;
206    GlobalContext.store('accountType', Number(dlpFile.dlpProperty.ownerAccountType));
207    const setAuthPermRet = decryptContent.setDlpGetAuthPerm();
208    if (setAuthPermRet.errcode !== Constants.ERR_CODE_SUCCESS) {
209      HiLog.error(TAG, 'setDlpGetAuthPerm error');
210      return ResultMsg.buildMsg(setAuthPermRet.errcode, setAuthPermRet.errmsg);
211    }
212    return ResultMsg.buildSuccess();
213  }
214
215  private async chargeOpenDLPFileError(err: BusinessError, decryptContent: DecryptContent):
216    Promise<Result<dlpPermission.DLPFile>> {
217    HiLog.info(TAG, 'begin chargeOpenDLPFileError');
218
219    // Not print sensitive information
220    if (err.code === Constants.ERR_JS_USER_NO_PERMISSION || err.code === Constants.ERR_JS_FILE_EXPIRATION) {
221      HiLog.error(TAG, `no permission or expiration, errcode is ${err.code}`);
222    } else {
223      HiLog.wrapError(TAG, err, 'openDLPFile error');
224    }
225
226    // Domain account open files with no permission
227    if (decryptContent.fileMetaInfo.accountType === dlpPermission.AccountType.DOMAIN_ACCOUNT &&
228      err.code === Constants.ERR_JS_USER_NO_PERMISSION && !decryptContent.openDlpFileData.isFromPlugin) {
229      let accountName: string = err.message.split(', contact:')?.[1];
230      let accountFlag = await AccountManager.checkAccountInfo(accountName);
231      if (!accountFlag) {
232        HiLog.error(TAG, 'checkAccountInfo error');
233        return ResultMsg.getErrMsg(Constants.ERR_JS_APP_NETWORK_INVALID);
234      }
235    }
236
237    // Error code conversion
238    if (err.code === Constants.ERR_JS_USER_NO_PERMISSION &&
239      decryptContent.fileMetaInfo.accountType === dlpPermission.AccountType.DOMAIN_ACCOUNT) {
240      return ResultMsg.buildMsg(Constants.ERR_JS_USER_NO_PERMISSION_2B, err.message);
241    }
242    if (err.code === Constants.ERR_JS_USER_NO_PERMISSION &&
243      decryptContent.fileMetaInfo.accountType === dlpPermission.AccountType.CLOUD_ACCOUNT) {
244      return ResultMsg.buildMsg(Constants.ERR_JS_USER_NO_PERMISSION_2C, err.message);
245    }
246    return ResultMsg.buildMsg(err.code, err.message);
247  }
248
249  private checkZipCancelByUser(decryptContent: DecryptContent): Result<DecryptStatus> {
250    const getRet = OpenDlpFileManager.getInstance().getStatus(decryptContent.openDlpFileData.uri);
251    if (getRet.errcode !== Constants.ERR_CODE_SUCCESS || !getRet.result) {
252      HiLog.error(TAG, 'checkZipCancelByUser getStatus error');
253      return ResultMsg.buildMsg(getRet.errcode, getRet.errmsg);
254    }
255    if (decryptContent.openDlpFileData.fileParse === FileParseType.ZIP &&
256    decryptContent.openDlpFileData.needShowToast) {
257      if (!OpeningDialogManager.getInstance().getIsDecryptingByRequestId(decryptContent.openDlpFileData.requestId)) {
258        HiLog.error(TAG, 'checkZipCancelByUser user close opening dialog');
259        return ResultMsg.getErrMsg(Constants.ERR_CODE_USER_STOP_DIALOG);
260      }
261    }
262    return ResultMsg.buildSuccess(getRet.result);
263  }
264
265  private async prepareCreateDecryptTask(decryptContent: DecryptContent): Promise<Result<void>> {
266    HiLog.info(TAG, 'start prepareCreateDecryptTask');
267    const checkRet = this.checkZipCancelByUser(decryptContent);
268    if (checkRet.errcode !== Constants.ERR_CODE_SUCCESS) {
269      HiLog.error(TAG, 'createDecryptTask checkZipCancelByUser failed');
270      return ResultMsg.buildMsg(checkRet.errcode, checkRet.errmsg);
271    }
272    await OpeningDialogManager.getInstance().showOpeningDialog(decryptContent.openDlpFileData.uri,
273      decryptContent.openDlpFileData.requestId, decryptContent.openDlpFileData.needShowToast);
274    ApplyEfficiencyManager.getInstance().applyEfficiency();
275    return ResultMsg.buildSuccess();
276  }
277
278  private async createDecryptTask(callerAppId: string,
279    decryptContent: DecryptContent): Promise<Result<dlpPermission.DLPFile>> {
280    HiLog.info(TAG, 'start createDecryptTask');
281    const prepareRet = await this.prepareCreateDecryptTask(decryptContent);
282    if (prepareRet.errcode !== Constants.ERR_CODE_SUCCESS) {
283      HiLog.error(TAG, 'prepareCreateDecryptTask failed');
284      return ResultMsg.buildMsg(prepareRet.errcode, prepareRet.errmsg);
285    }
286    const timeoutData = this.setupTimeoutPromise();
287    const timeout500 = this.setupNoDialogTimeout(decryptContent);
288    try {
289      const dlpFile = await this.executeDecryption(callerAppId, decryptContent, timeoutData.timeoutPromise);
290      HiLog.info(TAG, 'dlpPermission.openDLPFile success');
291      return ResultMsg.buildSuccess(dlpFile);
292    } catch (error) {
293      return await this.handleDecryptionError(error, decryptContent);
294    } finally {
295      await this.cleanupResources(timeoutData.timeout10, timeout500, decryptContent);
296    }
297  }
298
299  private setupTimeoutPromise(): TimeoutData {
300    let timeout10: number | undefined = undefined;
301    const timeoutPromise = new Promise<never>((_, reject) => {
302      timeout10 = setTimeout(() => {
303        HiLog.error(TAG, 'openDLPFile operation timed out after 10 seconds');
304        reject();
305      }, Constants.DECRYPT_TIMEOUT_TIME);
306    });
307    return { timeout10: timeout10, timeoutPromise: timeoutPromise };
308  }
309
310  private setupNoDialogTimeout(decryptContent: DecryptContent): number | undefined {
311    if (decryptContent.openDlpFileData.needShowToast) {
312      return undefined;
313    }
314    const timeout500 = setTimeout(async () => {
315      HiLog.error(TAG, 'No need show dialog, but decryption takes more than 500 ms.');
316      decryptContent.openDlpFileData.needShowToast = true;
317      await OpeningDialogManager.getInstance().showOpeningDialogByTimeout(decryptContent.openDlpFileData.requestId);
318    }, Constants.DECRYPT_NO_NEED_SHOW_DIALOG_TIMEOUT);
319    return timeout500;
320  }
321
322  private async executeDecryption(callerAppId: string, decryptContent: DecryptContent,
323    timeoutPromise: Promise<never>): Promise<dlpPermission.DLPFile> {
324    await new Promise<void>(resolve => setTimeout(resolve, 500));
325    return Promise.race<Promise<dlpPermission.DLPFile | never>>([
326      dlpPermission.openDLPFile(decryptContent.dlpFd, callerAppId),
327      timeoutPromise
328    ]);
329  }
330
331  private async handleDecryptionError(error: BusinessError,
332    decryptContent: DecryptContent): Promise<Result<dlpPermission.DLPFile>> {
333    HiLog.error(TAG, 'dlpPermission.openDLPFile error');
334    if (!OpeningDialogManager.getInstance().getIsDecryptingByRequestId(decryptContent.openDlpFileData.requestId)) {
335      HiLog.info(TAG, 'User stop Dialog');
336      return ResultMsg.getErrMsg(Constants.ERR_CODE_USER_STOP_DIALOG);
337    }
338    if (error) {
339      const busErr: BusinessError = error as BusinessError;
340      return await this.chargeOpenDLPFileError(busErr, decryptContent);
341    }
342    HiLog.error(TAG, 'openDLPFile error for timeout');
343    return ResultMsg.getErrMsg(Constants.ERR_CODE_DECRYPT_TIME_OUT);
344  }
345
346  private async cleanupResources(timeout10: number | undefined, timeout500: number | undefined,
347    decryptContent: DecryptContent): Promise<void> {
348    HiLog.info(TAG, 'createDecryptTask finally');
349    clearTimeout(timeout10);
350    clearTimeout(timeout500);
351    await OpeningDialogManager.getInstance().hideOpeningDialog(decryptContent.openDlpFileData.requestId);
352  }
353
354  private async getAppInfo(decryptContent: DecryptContent): Promise<Result<dlpPermission.DLPSandboxInfo>> {
355    let sandboxBundleName = decryptContent.openDlpFileData.sandboxBundleName;
356    let authPerm = decryptContent.authPerm;
357    let userId = decryptContent.userId;
358    let uri = decryptContent.openDlpFileData.uri;
359    try {
360      let appInfo: dlpPermission.DLPSandboxInfo;
361      appInfo = await dlpPermission.installDLPSandbox(sandboxBundleName, authPerm, userId, uri);
362      return ResultMsg.buildSuccess(appInfo);
363    } catch (error) {
364      HiLog.wrapError(TAG, error, 'installDLPSandbox error');
365      return ResultMsg.buildMsg(error.code, error.message);
366    }
367  }
368
369  private async getLinkInfo(decryptContent: DecryptContent): Promise<Result<void>> {
370    HiLog.info(TAG, 'start getLinkInfo');
371    const generateLinkFileNameRet = decryptContent.generateLinkFileName();
372    if (generateLinkFileNameRet.errcode !== Constants.ERR_CODE_SUCCESS) {
373      return ResultMsg.buildMsg(generateLinkFileNameRet.errcode, generateLinkFileNameRet.errmsg);
374    }
375
376    hiTraceMeter.startTrace('DlpAddLinkFileJs', decryptContent.openDlpFileData.startId);
377    const addRet = await this.addDLPLinkFile(decryptContent);
378    hiTraceMeter.finishTrace('DlpAddLinkFileJs', decryptContent.openDlpFileData.startId);
379    if (addRet.errcode !== Constants.ERR_CODE_SUCCESS) {
380      return ResultMsg.buildMsg(addRet.errcode, addRet.errmsg);
381    }
382
383    const generateLinkUriRet = decryptContent.generateLinkUri();
384    if (generateLinkUriRet.errcode !== Constants.ERR_CODE_SUCCESS) {
385      return ResultMsg.buildMsg(generateLinkUriRet.errcode, generateLinkUriRet.errmsg);
386    }
387
388    this.grandUriPermission(decryptContent);
389    return ResultMsg.buildSuccess();
390  }
391
392  private async addDLPLinkFile(decryptContent: DecryptContent): Promise<Result<void>> {
393    try {
394      await decryptContent.dlpFile.addDLPLinkFile(decryptContent.linkFileName);
395      return ResultMsg.buildSuccess();
396    } catch (error) {
397      HiLog.wrapError(TAG, error, 'addDLPLinkFile failed');
398      await closeDlpFile(decryptContent);
399      return ResultMsg.buildResult(error.code, error.message);
400    }
401  }
402
403  private async grandUriPermission(decryptContent: DecryptContent): Promise<void> {
404    const flag = wantConstant.Flags.FLAG_AUTH_WRITE_URI_PERMISSION |
405    wantConstant.Flags.FLAG_AUTH_PERSISTABLE_URI_PERMISSION;
406    const uri = decryptContent.openDlpFileData.uri;
407    const targetBundleName = decryptContent.openDlpFileData.sandboxBundleName;
408    try {
409      uriPermissionManager.grantUriPermission(uri, flag, targetBundleName);
410    } catch (error) {
411      HiLog.wrapError(TAG, error, 'grandUriPermission failed');
412    }
413  }
414
415  private async deleteDecryptData(decryptContent: DecryptContent): Promise<void> {
416    const manager = OpenDlpFileManager.getInstance();
417    const rmRet = await manager.removeAllByUri(decryptContent.openDlpFileData.uri);
418    if (rmRet.errcode !== Constants.ERR_CODE_SUCCESS) {
419      HiLog.error(TAG, 'deleteDecryptData failed');
420    }
421  }
422
423  private async checkDistributedInfoId(decryptContent: DecryptContent): Promise<boolean> {
424    if (decryptContent.fileMetaInfo.accountType === dlpPermission.AccountType.DOMAIN_ACCOUNT) {
425      HiLog.info(TAG, 'domain account no need to checkDistributedInfoId');
426      return true;
427    }
428    let historyDistributedInfoId = decryptContent.distributedInfoId;
429    let nowDistributedInfoId: string = '';
430    try {
431      let accountInfo = await getOsAccountInfo();
432      nowDistributedInfoId = accountInfo.distributedInfo.id;
433    } catch (error) {
434      HiLog.wrapError(TAG, error, 'Failed to get account info');
435      return false;
436    }
437    return historyDistributedInfoId === nowDistributedInfoId;
438  }
439}