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}