• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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}