1/** 2 * Copyright (c) 2024 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 util from '@ohos.util'; 17import { promptAction } from '@kit.ArkUI'; 18import { BusinessError } from '@kit.BasicServicesKit'; 19import { certificateManager } from '@kit.DeviceCertificateKit'; 20import VpnConfig, { IpsecVpnConfig, OpenVpnConfig } from './VpnConfig'; 21import VpnConstant from './VpnConstant'; 22import { VpnTypeModel } from './VpnTypeModel'; 23import { SwanCtlModel } from './SwanCtlModel'; 24import LogUtil from '../../../../../../../common/utils/src/main/ets/default/baseUtil/LogUtil'; 25 26const MODULE_TAG: string = 'setting_vpn:VpnConnectModel:'; 27const OVPN_PROTOCOL_TCP: number = 0; 28const OVPN_PROTOCOL_UDP: number = 1; 29 30/** 31 * system vpn config model 32 */ 33export class VpnConfigModel { 34 private static instance: VpnConfigModel; 35 36 private isNeedUpdateVpnList: boolean = false; 37 38 public static getInstance(): VpnConfigModel { 39 if (!this.instance) { 40 this.instance = new VpnConfigModel(); 41 } 42 return this.instance; 43 } 44 45 isUpdateVpnList(): boolean { 46 return this.isNeedUpdateVpnList; 47 } 48 49 setNeedUpdateVpnList(isUpdate: boolean): void { 50 this.isNeedUpdateVpnList = isUpdate; 51 } 52 53 setAddress(vpnConfig: VpnConfig, vpnAddress: string): void { 54 if (vpnConfig === undefined || vpnConfig === null) { 55 LogUtil.error(MODULE_TAG + `setAddress failed, invalid config`); 56 return; 57 } 58 vpnConfig.addresses = [{ 59 address: { address: vpnAddress }, 60 prefixLength: 1, 61 }] 62 } 63 64 getAddress(vpnConfig: VpnConfig): string | undefined { 65 if (vpnConfig === undefined || vpnConfig === null) { 66 LogUtil.error(MODULE_TAG + `setRoutes failed, invalid config`); 67 return undefined; 68 } 69 if (vpnConfig.addresses?.length > 0) { 70 return vpnConfig.addresses[0].address?.address; 71 } 72 LogUtil.error(MODULE_TAG + `getAddress invalid vpnConfig`); 73 return undefined; 74 } 75 76 stringToUint8Array(str: string): Uint8Array { 77 let arr: number[] = []; 78 for (let i = 0, j = str.length; i < j; ++i) { 79 arr.push(str.charCodeAt(i)); 80 } 81 return new Uint8Array(arr); 82 } 83 84 uint8ArrayToString(data: Uint8Array): string { 85 let resultStr: string = ''; 86 const charArray = data.map(value => Number(value)); 87 charArray.forEach((value) => { 88 resultStr += String.fromCharCode(value); 89 }); 90 return resultStr; 91 } 92 93 showToast(msg: string | Resource): void { 94 if (!msg) { 95 LogUtil.error(MODULE_TAG + `showToast failed, invalid msg`); 96 return; 97 } 98 promptAction.showToast({ 99 message: msg, 100 duration: 2000, 101 }); 102 } 103 104 inflateConfigFromFileData(config: OpenVpnConfig, data: string[]): void { 105 config.ovpnConfigFilePath = data[0]; 106 let content = data[1]; 107 config.ovpnConfigContent = content; 108 109 let regex = /proto\s+(tcp|udp)/; 110 let match = regex.exec(content); 111 if (match && match.length >= 1) { 112 config.ovpnProtocolFileRaw = match[0]; 113 config.ovpnProtocol = match[1] === 'udp' ? OVPN_PROTOCOL_UDP : OVPN_PROTOCOL_TCP; 114 } 115 116 regex = /remote\s+([^\s]+)\s+(\d+)/; 117 match = regex.exec(content); 118 if (match && match.length >= 2) { 119 config.ovpnAddressPortFileRaw = match[0]; 120 this.setAddress(config, match[1]); 121 config.ovpnPort = match[2]; 122 } 123 124 regex = /<ca>([\s\S]*?)<\/ca>/ 125 match = regex.exec(content); 126 if (match) { 127 config.ovpnCaCertFileRaw = match[0]; 128 } 129 130 regex = /<cert>([\s\S]*?)<\/cert>/ 131 match = regex.exec(content); 132 if (match && match.length >= 1) { 133 config.ovpnUserCertFileRaw = match[0]; 134 } 135 136 regex = /<key>([\s\S]*?)<\/key>/ 137 match = regex.exec(content); 138 if (match && match.length >= 1) { 139 config.ovpnPrivateKeyFileRaw = match[0]; 140 } 141 142 regex = /<auth-user-pass>\s*([\s\S]+?)\s*(\r?\n|\r)\s*([\s\S]+?)\s*<\/auth-user-pass>/ 143 match = regex.exec(content); 144 if (match && match.length >= 4) { 145 config.ovpnUserPassFileRaw = match[0]; 146 config.userName = match[1]; 147 config.password = match[3]; 148 } 149 150 regex = /http-proxy\s+([^\s]+)\s+(\d+)/; 151 match = regex.exec(content); 152 if (match && match.length >= 3) { 153 config.ovpnProxyHostFileRaw = match[0]; 154 config.ovpnProxyHost = match[1]; 155 config.ovpnProxyPort = match[2]; 156 } 157 158 regex = /<http-proxy-user-pass>\s*([\s\S]+?)\s*(\r?\n|\r)\s*([\s\S]+?)\s*<\/http-proxy-user-pass>/; 159 match = regex.exec(content); 160 if (match && match.length >= 4) { 161 config.ovpnProxyUserPassFileRaw = match[0]; 162 config.ovpnProxyUser = match[1]; 163 config.ovpnProxyPass = match[3]; 164 } 165 } 166 167 prepareAddSysVpnConfig(config: VpnConfig): void { 168 switch (config?.vpnType) { 169 case VpnTypeModel.TYPE_OPENVPN: 170 this.prepareOvpnConfig(config as OpenVpnConfig); 171 break; 172 default: 173 this.prepareIpsecConfig(config as IpsecVpnConfig); 174 break; 175 } 176 } 177 178 prepareIpsecConfig(config: IpsecVpnConfig): void { 179 if (config === undefined || config === null) { 180 LogUtil.error(MODULE_TAG + "prepareIpsecConfig faild, invalid param.") 181 return; 182 } 183 SwanCtlModel.getInstance().buildConfig(config); 184 } 185 186 prepareOvpnConfig(config: OpenVpnConfig): void { 187 if (config === undefined || config === null) { 188 LogUtil.error(MODULE_TAG + "prepareOvpnConfig faild, invalid param.") 189 return; 190 } 191 let that = new util.Base64Helper(); 192 let content = ''; 193 if (config.ovpnConfig) { 194 let data = that.decodeSync(config.ovpnConfig); 195 content = this.uint8ArrayToString(data); 196 } else { 197 content = config.ovpnConfigContent ?? 'client\ndev tun'; 198 } 199 200 let protocolReplace: string = `proto ${config.ovpnProtocol === OVPN_PROTOCOL_TCP ? 'tcp' : 'udp'}`; 201 if (protocolReplace === config.ovpnProtocolFileRaw) { 202 content = content.replace(config.ovpnProtocolFileRaw, '\n' + protocolReplace); 203 } else { 204 content += '\n'; 205 content += protocolReplace; 206 } 207 208 let vpnAddress: string = this.getAddress(config); 209 if (vpnAddress && config.ovpnPort) { 210 let addressPortReplace: string = `remote ${vpnAddress} ${config.ovpnPort}`; 211 if (addressPortReplace === config.ovpnAddressPortFileRaw) { 212 content = content.replace(config.ovpnAddressPortFileRaw, '\n' + addressPortReplace); 213 } else { 214 content += '\n'; 215 content += addressPortReplace; 216 content += '\nlog \/data\/service\/el1\/public\/netmanager\/config.log\ndev-node \/dev\/tun' + 217 '\nresolv-retry infinite\nnobind\npersist-key\npersist-tun\nverb 7\n'; 218 } 219 } 220 221 if (config.ovpnCaCert) { 222 let caCert: string = config.ovpnCaCert; 223 if (config.ovpnCaCertFileRaw) { 224 if (config.ovpnCaCertFileRaw !== caCert) { 225 content = content.replace(config.ovpnCaCertFileRaw, '\n' + caCert); 226 } 227 } else { 228 content += '\n'; 229 content += caCert; 230 } 231 } 232 233 if (config.ovpnAuthType === VpnConstant.OVPN_AUTH_TYPE_PWD) { 234 config.ovpnUserCert = undefined; 235 if (config.ovpnUserCertFileRaw) { 236 content.replace(config.ovpnUserCertFileRaw, ''); 237 } 238 config.ovpnUserCertFileRaw = undefined; 239 config.ovpnUserCertFilePath = undefined; 240 } 241 242 if (config.ovpnUserCert) { 243 let userCert: string = config.ovpnUserCert; 244 if (config.ovpnUserCertFileRaw) { 245 if (config.ovpnUserCertFileRaw !== userCert) { 246 content = content.replace(config.ovpnUserCertFileRaw, '\n' + userCert); 247 } 248 } else { 249 content += '\n'; 250 content += userCert; 251 } 252 } 253 254 if (config.ovpnAuthType === VpnConstant.OVPN_AUTH_TYPE_PWD) { 255 config.ovpnPrivateKey = undefined; 256 if (config.ovpnPrivateKeyFileRaw) { 257 content.replace(config.ovpnPrivateKeyFileRaw, ''); 258 } 259 config.ovpnPrivateKeyFileRaw = undefined; 260 config.ovpnPrivateKeyFilePath = undefined; 261 } 262 263 if (config.ovpnPrivateKey) { 264 let privateKey: string = config.ovpnPrivateKey; 265 if (config.ovpnPrivateKeyFileRaw) { 266 if (config.ovpnPrivateKeyFileRaw !== privateKey) { 267 content = content.replace(config.ovpnPrivateKeyFileRaw, '\n' + privateKey); 268 } 269 } else { 270 content += '\n'; 271 content += privateKey; 272 } 273 } 274 275 if (config.ovpnAuthType === VpnConstant.OVPN_AUTH_TYPE_TLS) { 276 config.userName = undefined; 277 config.password = undefined; 278 if (config.ovpnUserPassFileRaw) { 279 content = content.replace(config.ovpnUserPassFileRaw, ''); 280 } 281 } 282 283 if (config.userName) { 284 let userPass: string = '<auth-user-pass>'; 285 userPass += '\n'; 286 userPass += config.userName; 287 userPass += '\n'; 288 userPass += config.password; 289 userPass += '\n'; 290 userPass += '</auth-user-pass>' 291 292 if (config.userName) { 293 if (config.ovpnUserPassFileRaw) { 294 if (config.ovpnUserPassFileRaw !== userPass) { 295 content = content.replace(config.ovpnUserPassFileRaw, '\n' + userPass); 296 } 297 } else { 298 content += '\n'; 299 content += userPass; 300 } 301 } 302 } 303 304 if (config.ovpnProxyHost) { 305 let proxyHostPort: string = `http-proxy ${config.ovpnProxyHost} ${config.ovpnProxyPort ?? ''}`; 306 if (config.ovpnProxyHostFileRaw) { 307 if (config.ovpnProxyHostFileRaw !== proxyHostPort) { 308 content = content.replace(config.ovpnProxyHostFileRaw, '\n' + proxyHostPort); 309 } 310 } else { 311 content += '\n'; 312 content += proxyHostPort; 313 } 314 } 315 316 if (config.ovpnProxyUser) { 317 let proxyUserPass: string = '<http-proxy-user-pass>'; 318 proxyUserPass += '\n'; 319 proxyUserPass += config.ovpnProxyUser; 320 proxyUserPass += '\n'; 321 proxyUserPass += config.ovpnProxyPass; 322 proxyUserPass += '\n'; 323 proxyUserPass += '</http-proxy-user-pass>' 324 if (config.ovpnProxyUserPassFileRaw) { 325 if (config.ovpnProxyUserPassFileRaw !== proxyUserPass) { 326 content = content.replace(config.ovpnProxyUserPassFileRaw, '\n' + proxyUserPass); 327 } 328 } else { 329 content += '\n'; 330 content += proxyUserPass; 331 } 332 } 333 334 let regex = /^\s*$\n/gm; 335 content = content.replace(regex, ''); 336 config.ovpnConfig = that.encodeToStringSync(this.stringToUint8Array(content)); 337 } 338 339 async getCAList(callback: Function): Promise<void> { 340 LogUtil.info(MODULE_TAG + 'getCAList start'); 341 try { 342 let result = await certificateManager.getAllUserTrustedCertificates(); 343 let certList: VpnCertItem[] = []; 344 if (result?.certList !== undefined) { 345 LogUtil.info(MODULE_TAG + 'getCAList end size=' + result.certList.length); 346 for (let i = 0; i < result.certList.length; i++) { 347 if (String(result.certList[i].uri).indexOf('u=0;') === -1) { 348 certList.push(new VpnCertItem( 349 String(result.certList[i].certAlias), String(result.certList[i].uri))); 350 } 351 } 352 callback(CMModelErrorCode.CM_MODEL_ERROR_SUCCESS, certList); 353 } else { 354 LogUtil.error(MODULE_TAG + 'getCAList failed, undefined'); 355 callback(CMModelErrorCode.CM_MODEL_ERROR_FAILED, undefined); 356 } 357 } catch (err) { 358 let e: BusinessError = err as BusinessError; 359 LogUtil.error(MODULE_TAG + 'getCAList err, message: ' + e.message + ', code: ' + e.code); 360 callback(CMModelErrorCode.CM_MODEL_ERROR_EXCEPTION); 361 } 362 } 363 364 async getSystemAppCertList(callback: Function): Promise<void> { 365 LogUtil.info(MODULE_TAG + 'getSystemAppCertList start'); 366 try { 367 let result = await certificateManager.getAllSystemAppCertificates(); 368 let certList: VpnCertItem[] = []; 369 if (result?.credentialList !== undefined) { 370 LogUtil.info(MODULE_TAG + 'getSystemAppCertList size=' + result.credentialList.length); 371 for (let i = 0; i < result.credentialList.length; i++) { 372 certList.push(new VpnCertItem( 373 String(result.credentialList[i].alias), String(result.credentialList[i].keyUri))); 374 } 375 callback(CMModelErrorCode.CM_MODEL_ERROR_SUCCESS, certList); 376 } else { 377 LogUtil.error(MODULE_TAG + 'getSystemAppCertList failed, undefined.'); 378 callback(CMModelErrorCode.CM_MODEL_ERROR_FAILED, undefined); 379 } 380 } catch (err) { 381 let e: BusinessError = err as BusinessError; 382 LogUtil.error(MODULE_TAG + 'getSystemAppCertList failed with err, message: ' + e.message + ', code: ' + e.code); 383 callback(CMModelErrorCode.CM_MODEL_ERROR_EXCEPTION); 384 } 385 } 386} 387 388export class VpnCertItem { 389 certAlias: string; 390 certUri: string; 391 392 constructor(alias: string, uri: string) { 393 this.certAlias = alias; 394 this.certUri = uri; 395 } 396} 397 398enum CMModelErrorCode { 399 CM_MODEL_ERROR_SUCCESS = 0, 400 CM_MODEL_ERROR_FAILED = -1, 401 CM_MODEL_ERROR_EXCEPTION = -2, 402 CM_MODEL_ERROR_UNKNOWN_OPT = -3, 403 CM_MODEL_ERROR_NOT_SUPPORT = -4, 404 CM_MODEL_ERROR_NOT_FOUND = -5, 405 CM_MODEL_ERROR_INCORRECT_FORMAT = -6, 406 CM_MODEL_ERROR_MAX_QUANTITY_REACHED = -7, 407 CM_MODEL_ERROR_ALIAS_LENGTH_REACHED_LIMIT = -8 408}