diff --git a/code/DocsSample/NetWork_Kit/NetWorkKit_NetManager/VPNControl_Case/entry/src/main/ets/common/VpnConfig.ts b/code/DocsSample/NetWork_Kit/NetWorkKit_NetManager/VPNControl_Case/entry/src/main/ets/common/VpnConfig.ts new file mode 100644 index 0000000000000000000000000000000000000000..3d267ddf247c0fac3803e4f6c818c6fdcf128005 --- /dev/null +++ b/code/DocsSample/NetWork_Kit/NetWorkKit_NetManager/VPNControl_Case/entry/src/main/ets/common/VpnConfig.ts @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import vpn from '@ohos.net.vpn'; +import { VpnTypeModel } from './VpnTypeModel'; + +/** + * extend system VpnConig + */ +export default class VpnConfig implements vpn.SysVpnConfig { + // vpnConfig + addresses: Array = []; + routes?: Array; + dnsAddresses?: Array; + searchDomains?: Array; + isLegacy: boolean = true; + + // sysVpnConfig + vpnId: string = ''; + vpnName: string = ''; + vpnType: vpn.SysVpnType = VpnTypeModel.TYPE_IKEV2_IPSEC_MSCHAPv2; + userName?: string; + password?: string; + saveLogin: boolean = false; + userId?: number; + forwardingRoutes?: string; + trustedApplications: Array = []; + blockedApplications: Array = []; + pkcs12Password?: string; + pkcs12FileData?: Uint8Array; +} + +export class OpenVpnConfig extends VpnConfig implements vpn.OpenVpnConfig { + ovpnConfigFilePath?: string // openVpn file name + ovpnConfigContent?: string // openVpn config + ovpnConfig?: string // openVpn config base64 + ovpnAuthType: number = 0 // openvpn auth type + ovpnProtocolFileRaw?: string // Protocol + ovpnProtocol: number = 0 // 0:tcp 1:udp + ovpnAddressPortFileRaw?: string // port + ovpnPort?: string // openvpn port + askpass: string // private key password + ovpnCaCertFilePath?: string // openVpn CA FilePath + ovpnUserCertFilePath?: string // openVpn USER FilePath + ovpnPrivateKeyFilePath?: string // openVpn private key FilePath + ovpnCaCertFileRaw?: string // ca raw data + ovpnCaCert?: string // CA data + ovpnUserCertFileRaw?: string // + ovpnUserCert?: string // + ovpnPrivateKeyFileRaw?: string // private key raw data + ovpnPrivateKey?: string // private key data + ovpnUserPassFileRaw?: string // userpass raw d + ovpnProxyHostFileRaw?: string // host raw data + ovpnProxyHost?: string //ovpn host + ovpnProxyPort?: string //ovpn port + ovpnProxyUserPassFileRaw?: string // userpass data + ovpnProxyUser?: string //ovpn user + ovpnProxyPass?: string //ovpn pass +} + +export class IpsecVpnConfig extends VpnConfig implements vpn.IpsecVpnConfig { + ipsecIdentifier?: string // ipsec identifier + ipsecPreSharedKey?: string // ipsec pre sharedKey + l2tpSharedKey?: string // L2TP secret + ipsecPublicUserCertConfig?: string // public userCert config + ipsecPublicUserCertFilePath?: string // public userCert FilePath + ipsecPrivateUserCertConfig?: string // private userCert config + ipsecPrivateUserCertFilePath?: string // private userCert FilePath + ipsecCaCertConfig?: string // ca config + ipsecCaCertFilePath?: string // ca FilePath + ipsecPublicServerCertConfig?: string // public serverCert config + ipsecPublicServerCertFilePath?: string // public serverCert FilePath + ipsecPrivateServerCertConfig?: string // private serverCert config + ipsecPrivateServerCertFilePath?: string // private serverCert FilePath + swanctlConfig?: string // swanctl config base64 + strongSwanConfig?: string // strongswan config base64 + optionsL2tpdClient?: string // optionsL2tpd Client base64 + xl2tpdConfig?: string // xl2tpd config base64 + ipsecConfig?: string // swanctl config base64 + ipsecSecrets?: string // swanctl config base64 +} + +export class VpnListItem { + vpnName: string //vpnName + vpnId: string //UUID +} \ No newline at end of file diff --git a/code/DocsSample/NetWork_Kit/NetWorkKit_NetManager/VPNControl_Case/entry/src/main/ets/common/VpnTypeModel.ts b/code/DocsSample/NetWork_Kit/NetWorkKit_NetManager/VPNControl_Case/entry/src/main/ets/common/VpnTypeModel.ts new file mode 100644 index 0000000000000000000000000000000000000000..4f340e23400e607c94d6a3e459efb8124ae1fd8b --- /dev/null +++ b/code/DocsSample/NetWork_Kit/NetWorkKit_NetManager/VPNControl_Case/entry/src/main/ets/common/VpnTypeModel.ts @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * support vpn type & displayname + */ +export class VpnTypeModel { + static readonly TYPE_IKEV2_IPSEC_MSCHAPv2: number = 1; // vpn.SysVpnType.IKEV2_IPSEC_MSCHAPv2; + static readonly TYPE_IKEV2_IPSEC_PSK: number = 2; // vpn.SysVpnType.IKEV2_IPSEC_PSK; + static readonly TYPE_IKEV2_IPSEC_RSA: number = 3; // vpn.SysVpnType.IKEV2_IPSEC_RSA; + static readonly TYPE_L2TP_IPSEC_PSK: number = 4; // vpn.SysVpnType.L2TP_IPSEC_PSK; + static readonly TYPE_L2TP_IPSEC_RSA: number = 5; // vpn.SysVpnType.L2TP_IPSEC_RSA; + static readonly TYPE_IPSEC_XAUTH_PSK: number = 6; // vpn.SysVpnType.IPSEC_XAUTH_PSK; + static readonly TYPE_IPSEC_XAUTH_RSA: number = 7; // vpn.SysVpnType.IPSEC_XAUTH_RSA; + static readonly TYPE_IPSEC_HYBRID_RSA: number = 8; // vpn.SysVpnType.IPSEC_HYBRID_RSA; + static readonly TYPE_OPENVPN: number = 9; // vpn.SysVpnType.OPENVPN; + static readonly TYPE_L2TP: number = 10; + + private static instance: VpnTypeModel; + + public static getInstance(): VpnTypeModel { + if (!this.instance) { + this.instance = new VpnTypeModel(); + } + return this.instance; + } + + public uint8ArrayToString(fileData): string { + let dataString = ''; + for (let i = 0; i < fileData.length; i++) { + dataString += String.fromCharCode(fileData[i]); + } + return dataString; + } + + public stringToUint8Array(str: string): Uint8Array { + let arr: number[] = []; + for (let i = 0, j = str.length; i < j; ++i) { + arr.push(str.charCodeAt(i)); + } + return new Uint8Array(arr); + } +} \ No newline at end of file diff --git a/code/DocsSample/NetWork_Kit/NetWorkKit_NetManager/VPNControl_Case/entry/src/main/ets/pages/Index.ets b/code/DocsSample/NetWork_Kit/NetWorkKit_NetManager/VPNControl_Case/entry/src/main/ets/pages/Index.ets index aee7d5d54eeb08d0cdc0d2d1992d202be1390f68..40912255d702bb8c323c9624b159d77553b73edf 100644 --- a/code/DocsSample/NetWork_Kit/NetWorkKit_NetManager/VPNControl_Case/entry/src/main/ets/pages/Index.ets +++ b/code/DocsSample/NetWork_Kit/NetWorkKit_NetManager/VPNControl_Case/entry/src/main/ets/pages/Index.ets @@ -80,6 +80,25 @@ struct Index { .id(ComponentId.STOP_VPN_BUTTON) .width('70%') .fontSize(BUTTON_FONT_SIZE) + .margin(BUTTON_MARGIN) + + // Sys VPN 按钮 + Button('start sysvpn') + .onClick(() => { + Logger.info('developTag', '%{public}s', 'Succeeded in clicking the button.'); + router.pushUrl({ url: 'pages/SysVpn' }).then(() => { + Logger.info('developTag', '%{public}s', 'Succeeded in jumping to the second page.'); + this.flag = 'Pass'; + }).catch((err: BusinessError) => { + Logger.error('developTag', 'Failed to jump to the second page: %{public}s', + JSON.stringify(err) ?? ''); + this.flag = 'Error'; + }); + }) + .id(ComponentId.STOP_VPN_BUTTON) + .width('70%') + .fontSize(BUTTON_FONT_SIZE) + .margin(BUTTON_MARGIN) }.width('100%') }.height('100%') } diff --git a/code/DocsSample/NetWork_Kit/NetWorkKit_NetManager/VPNControl_Case/entry/src/main/ets/pages/SysVpn.ets b/code/DocsSample/NetWork_Kit/NetWorkKit_NetManager/VPNControl_Case/entry/src/main/ets/pages/SysVpn.ets new file mode 100644 index 0000000000000000000000000000000000000000..e4c0534dd17d9bf5c4faebf8cf655ee742eb8aaf --- /dev/null +++ b/code/DocsSample/NetWork_Kit/NetWorkKit_NetManager/VPNControl_Case/entry/src/main/ets/pages/SysVpn.ets @@ -0,0 +1,527 @@ +/** + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { vpn, vpnExtension } from "@kit.NetworkKit"; +import { common, Want } from "@kit.AbilityKit"; +import { certificateManager } from "@kit.DeviceCertificateKit"; +import { BusinessError } from "@kit.BasicServicesKit"; +import { picker } from "@kit.CoreFileKit"; +import Logger from "../common/Logger"; + +const TAG: string = 'vpn_demo:index:'; + +export class VpnCertItem { + certAlias: string; + certUri: string; + + constructor(alias: string, uri: string) { + this.certAlias = alias; + this.certUri = uri; + } +} + +@Entry +@Component +export struct SysVpn { + @State l2tpVpnState: string = ''; + @State ikev2PskState: string = ''; + @State ikev2RsaState: string = ''; + @State l2tpPskState: string = ''; + @State l2tpRsaState: string = ''; + @State ipsecMschapV2RsaState: string = ''; + @State ikev1XauthState: string = ''; + @State vpnState: string = ''; + @State caCertList: VpnCertItem[] = []; + @State userCertList: VpnCertItem[] = []; + @State caCertUri: string = ''; + @State userCertUri: string = ''; + @State trustedApplications: string = ''; + @State serviceAddr: string = ''; + @State routerAddr: string = ''; + @State routerPrefixLength: number = 24; + @State pkcs12FilePath: string = ''; + @State pkcs12Password: string = ''; + + async aboutToAppear(): Promise { + vpn.on('connectMulti', (state) => { + Logger.info(`${TAG} , on_multi :: state = ${JSON.stringify(state)}`) + if (state) { + let isConnected = state['isConnected'] as boolean; + let bundleName = state['bundleName'] as boolean; + if (isConnected === true) { + this.vpnState = 'vpn 已连接'; + } + if (isConnected === false) { + this.vpnState = 'vpn 已断开'; + } + } + }) + try { + let result = await certificateManager.getAllUserTrustedCertificates(); + if (result?.certList !== undefined) { + Logger.info(TAG + 'getCAList end size=' + result.certList.length); + for (let i = 0; i < result.certList.length; i++) { + if (String(result.certList[i].uri).indexOf('u=0;') === -1) { + this.caCertList.push(new VpnCertItem( + String(result.certList[i].certAlias), String(result.certList[i].uri))); + } + } + } else { + Logger.error(TAG + 'getCAList failed, undefined'); + } + } catch (err) { + let e: BusinessError = err as BusinessError; + Logger.error(TAG + 'getCAList err, message: ' + e.message + ', code: ' + e.code); + } + + try { + let result = await certificateManager.getAllPublicCertificates(); + if (result?.certList !== undefined) { + Logger.info(TAG + 'getUserList end size=' + result.certList.length); + for (let i = 0; i < result.certList.length; i++) { + if (String(result.certList[i].uri).indexOf('u=0;') === -1) { + this.userCertList.push(new VpnCertItem( + String(result.certList[i].certAlias), String(result.certList[i].uri))); + } + } + } else { + Logger.error(TAG + 'getUserList failed, undefined'); + } + } catch (err) { + let e: BusinessError = err as BusinessError; + Logger.error(TAG + 'getUserList err, message: ' + e.message + ', code: ' + e.code); + } + } + + aboutToDisappear(): void { + vpn.off('connectMulti', () => { + Logger.info(`${TAG} , vpn multi off`); + }) + } + + showResult(functionName: string | undefined, content: string | undefined) { + AlertDialog.show( + { + title: functionName ?? "", + message: content ?? "", + autoCancel: true, + offset: { dx: 0, dy: -20 }, + gridCount: 3, + confirm: { + value: '关闭', + action: () => { + console.info('Button-clicking callback') + } + }, + cancel: () => { + console.info('Closed callbacks') + }, + alignment: DialogAlignment.Center + } + ) + } + + onConnectClick(value: string) { + Logger.info(`${TAG} , start connect vpn :: ${value}`); + Logger.info(`${TAG} , start connect vpn cert path :: ${this.pkcs12FilePath}`); + Logger.info(`${TAG} , start connect vpn cert password :: ${this.pkcs12Password}`); + + if(value.includes('rsa') && !this.pkcs12FilePath.includes('.p12')) { + this.showResult("证书选择", "请先加载pkcs12证书"); + return; + } + + if(value.includes('rsa') && this.pkcs12Password == '') { + this.showResult("证书密码输入", "请先加载pkcs12证书密码"); + return; + } + Logger.info(`${TAG} , trustedApplications = ${JSON.stringify(this.trustedApplications)}`); + let want: Want = { + bundleName: 'com.samples.vpncontrol_case', + abilityName: 'SysVpnExtAbility', + parameters: { + 'connect': value, + 'caCertUri': this.caCertUri, + 'userCertUri': this.userCertUri, + 'trustedApplications':this.trustedApplications, + 'serviceAddr':this.serviceAddr, + 'routerAddr':this.routerAddr, + 'routerPrefixLength':this.routerPrefixLength, + 'pkcs12FilePath':this.pkcs12FilePath, + 'pkcs12Password':this.pkcs12Password + } + }; + vpnExtension.startVpnExtensionAbility(want); + } + + onDisconnectClick(value: string) { + Logger.info(`${TAG} , start disconnect vpn :: ${value}`); + let want: Want = { + bundleName: "com.samples.vpncontrol_case", + abilityName: "SysVpnExtAbility", + parameters: { + 'disconnect': value + } + }; + vpnExtension.startVpnExtensionAbility(want); + } + + build() { + Scroll(){ + Column() { + Button('授权') + .onClick(() => { + let want: Want = { + bundleName: "com.samples.vpncontrol_case", + abilityName: "SysVpnExtAbility", + }; + vpnExtension.startVpnExtensionAbility(want); + }) + .width('80%') + .fontSize(20) + .margin(16) + .stateEffect(true) + .alignSelf(ItemAlign.Center) + + Row() { + Text('vpn 状态').fontSize(20) + Text(this.vpnState) + .fontSize(20) + .fontColor(Color.Blue) + .margin({ right: 84 }) + }.width('100%').justifyContent(FlexAlign.SpaceBetween).margin({ + top: 24, + left: 48, + }) + + Row(){ + TextInput({placeholder:'白名单'}).fontSize(20).onChange((value)=>{ + this.trustedApplications = value.trim(); + }) + }.margin({ + top:24, + left:24, + right:24 + }) + + Row(){ + Button('添加pkcs12证书').onClick(() => { + try { + let context = getContext(this) as common.Context; // 请确保getContext(this)返回结果为UIAbilityContext + let documentSelectOptions = new picker.DocumentSelectOptions(); + let documentPicker = new picker.DocumentViewPicker(); + documentPicker.select(documentSelectOptions).then((documentSelectResult: Array) => { + Logger.info(TAG + 'DocumentViewPicker.select successfully, documentSelectResult uri: ' + JSON.stringify(documentSelectResult)); + this.pkcs12FilePath = documentSelectResult[0]; + }).catch((err: BusinessError) => { + Logger.info(TAG + 'DocumentViewPicker.select failed with err: ' + JSON.stringify(err)); + }); + } catch (error) { + let err: BusinessError = error as BusinessError; + Logger.info(TAG + 'DocumentViewPicker failed with err: ' + JSON.stringify(err)); + } + }) + .fontSize(20) + .margin(16) + .stateEffect(true) + .width(200) + .bindMenu(this.CaMenu) + } + + Row(){ + Text('证书路径:' + this.pkcs12FilePath) + }.margin({ + top:24, + left:24, + right:24 + }) + + Row(){ + TextInput({placeholder:'证书密码'}).fontSize(20).onChange((value)=>{ + this.pkcs12Password = value; + }) + }.margin({ + top:24, + left:24, + right:24 + }) + + Row(){ + TextInput({placeholder:'服务器地址'}).fontSize(20).onChange((value)=>{ + this.serviceAddr = value; + }) + }.margin({ + top:24, + left:24, + right:24 + }) + + // IKEV2_IPSEC_PSK + Column() { + Row() { + Text('IKEV2_IPSEC_PSK') + .fontSize(20) + Text(this.ikev2PskState) + .fontSize(20).fontColor(Color.Blue).margin({ right: 36 }) + }.margin({ + bottom: 10 + }).width('100%').justifyContent(FlexAlign.SpaceBetween) + + Row() { + Button('连接') + .onClick(() => { + this.onConnectClick('ikev2_ipsec_psk'); + this.ikev2PskState = '已连接' + }) + .fontSize(20) + .margin(16) + .stateEffect(true) + .width(100) + + Button('断开') + .onClick(() => { + this.onDisconnectClick('ikev2_ipsec_psk'); + this.ikev2PskState = '已断开' + }) + .fontSize(20) + .margin(16) + .stateEffect(true) + .width(100) + }.width('100%').justifyContent(FlexAlign.SpaceBetween) + }.margin({ + top: 24, + left: 48, + right: 48 + }) + + // IKEV2_IPSEC_RSA 1 + Column() { + Row() { + Text('IKEV2_IPSEC_RSA') + .fontSize(20) + Text(this.ikev2RsaState) + .fontSize(20).fontColor(Color.Blue).margin({ right: 36 }) + }.margin({ + bottom: 10 + }).width('100%').justifyContent(FlexAlign.SpaceBetween) + + Row() { + Button('连接') + .onClick(() => { + this.onConnectClick('ikev2_ipsec_rsa'); + this.ikev2RsaState = '已连接' + }) + .fontSize(20) + .margin(16) + .stateEffect(true) + .width(100) + + Button('断开') + .onClick(() => { + this.onDisconnectClick('ikev2_ipsec_rsa'); + this.ikev2RsaState = '已断开' + }) + .fontSize(20) + .margin(16) + .stateEffect(true) + .width(100) + }.width('100%').justifyContent(FlexAlign.SpaceBetween) + }.margin({ + top: 24, + left: 48, + right: 48 + }) + + // l2tp_psk + Column() { + Row() { + Text('L2TP_IPSEC_PSK') + .fontSize(20) + Text(this.l2tpPskState) + .fontSize(20).fontColor(Color.Blue).margin({ right: 36 }) + }.margin({ + bottom: 10 + }).width('100%').justifyContent(FlexAlign.SpaceBetween) + + Row() { + Button('连接') + .onClick(() => { + this.onConnectClick('l2tp_ipsec_psk'); + this.l2tpPskState = '已连接' + }) + .fontSize(20) + .margin(16) + .stateEffect(true) + .width(100) + + Button('断开') + .onClick(() => { + this.onDisconnectClick('l2tp_ipsec_psk'); + this.l2tpPskState = '已断开' + }) + .width(100) + .fontSize(20) + .margin(16) + .stateEffect(true) + }.width('100%').justifyContent(FlexAlign.SpaceBetween) + }.margin({ + top: 24, + left: 48, + right: 48 + }) + + // l2tp + Column() { + Row() { + Text('L2TP') + .fontSize(20) + Text(this.l2tpVpnState) + .fontSize(20).fontColor(Color.Blue).margin({ right: 36 }) + }.margin({ + bottom: 10 + }).width('100%').justifyContent(FlexAlign.SpaceBetween) + + Row() { + Button('连接') + .onClick(() => { + this.onConnectClick('l2tp'); + this.l2tpVpnState = '已连接' + }) + .fontSize(20) + .margin(16) + .stateEffect(true) + .width(100) + + Button('断开') + .onClick(() => { + this.onDisconnectClick('l2tp'); + this.l2tpVpnState = '已断开' + }) + .fontSize(20) + .margin(16) + .stateEffect(true) + .width(100) + }.width('100%').justifyContent(FlexAlign.SpaceBetween) + }.margin({ + top: 24, + left: 48, + right: 48 + }) + + // l2tp_ipsce_rsa + Column() { + Row() { + Text('L2TP_IPSEC_RSA') + .fontSize(20) + Text(this.l2tpRsaState) + .fontSize(20).fontColor(Color.Blue).margin({ right: 36 }) + }.margin({ + bottom: 10 + }).width('100%').justifyContent(FlexAlign.SpaceBetween) + + Row() { + Button('连接') + .onClick(() => { + this.onConnectClick('l2tp_ipsec_rsa'); + this.l2tpRsaState = '已连接' + }) + .fontSize(20) + .margin(16) + .stateEffect(true) + .width(100) + + Button('断开') + .onClick(() => { + this.onDisconnectClick('l2tp_ipsec_rsa'); + this.l2tpRsaState = '已断开' + }) + .fontSize(20) + .margin(16) + .stateEffect(true) + .width(100) + }.width('100%').justifyContent(FlexAlign.SpaceBetween) + }.margin({ + top: 24, + left: 48, + right: 48 + }) + + // ike2-mschap + Column() { + Row() { + Text('IKEV2_MSCHAP_RSA') + .fontSize(20) + Text(this.ipsecMschapV2RsaState) + .fontSize(20).fontColor(Color.Blue).margin({ right: 36 }) + }.margin({ + bottom: 10 + }).width('100%').justifyContent(FlexAlign.SpaceBetween) + + Row() { + Button('连接') + .onClick(() => { + this.onConnectClick('ike2_mschap_rsa'); + this.ipsecMschapV2RsaState = '已连接' + }) + .fontSize(20) + .margin(16) + .stateEffect(true) + .width(100) + + Button('断开') + .onClick(() => { + this.onDisconnectClick('ike2_mschap_rsa'); + this.ipsecMschapV2RsaState = '已断开' + }) + .fontSize(20) + .margin(16) + .stateEffect(true) + .width(100) + }.width('100%').justifyContent(FlexAlign.SpaceBetween) + }.margin({ + top: 24, + left: 48, + right: 48 + }) + } + .alignItems(HorizontalAlign.Start) + .backgroundColor($r('sys.color.ohos_id_color_sub_background')) + .width('100%') + }.height('100%').expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM]) + } + + @Builder + CaMenu() { + Menu() { + ForEach(this.caCertList, ((item: VpnCertItem, index: number) => { + MenuItem({ content: item.certAlias }).onClick(() => { + Logger.info(`${TAG} , certUri = ${item.certUri}`); + this.caCertUri = item.certUri; + }) + }), (item: VpnCertItem) => JSON.stringify(item)) + } + } + + @Builder + UserMenu() { + Menu() { + ForEach(this.userCertList, ((item: VpnCertItem, index: number) => { + MenuItem({ content: item.certAlias }).onClick(() => { + Logger.info(`${TAG} , certUri = ${item.certUri}`); + this.userCertUri = item.certUri; + }) + }), (item: VpnCertItem) => JSON.stringify(item)) + } + } +} \ No newline at end of file diff --git a/code/DocsSample/NetWork_Kit/NetWorkKit_NetManager/VPNControl_Case/entry/src/main/ets/vpnability/SysVpnExtAbility.ets b/code/DocsSample/NetWork_Kit/NetWorkKit_NetManager/VPNControl_Case/entry/src/main/ets/vpnability/SysVpnExtAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..c003e0b9aa4a7c9fedea91f0d4f6ff0956a7d761 --- /dev/null +++ b/code/DocsSample/NetWork_Kit/NetWorkKit_NetManager/VPNControl_Case/entry/src/main/ets/vpnability/SysVpnExtAbility.ets @@ -0,0 +1,431 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Want } from '@kit.AbilityKit'; +import VpnExtensionAbility from '@ohos.app.ability.VpnExtensionAbility'; +import { vpn, vpnExtension } from '@kit.NetworkKit'; +import Logger from '../common/Logger'; +import VpnConfig, { IpsecVpnConfig } from '../common/VpnConfig'; +import { VpnTypeModel } from '../common/VpnTypeModel'; +import fs from '@ohos.file.fs'; +import { BusinessError } from '@kit.BasicServicesKit'; + + +const TAG: string = "SysVpnExtAbility:: "; + +export default class SysVpnExtAbility extends VpnExtensionAbility { + private vpnConnection: vpnExtension.VpnConnection | undefined = undefined; + private ikev1XauthId: string = ''; + private ikev2PskId: string = ''; + private ikev2RsaId: string =''; + private ike2MschapRsaId: string = ''; + private l2tpPskId: string = ''; + private l2tpId: string = ''; + private l2tpId_2: string = ''; + private l2tpId_3: string = ''; + private trustedApplications: Array = []; + private caUri: string = ''; + private userUri: string = ''; + private serviceAddr: string = ''; + private routerAddr: string = ''; + private routerPrefixLength: number = 16; + private pkcs12Uint8Array: Uint8Array = new Uint8Array(0); + private pkcs12Password: string = ''; + ikev1XauthPskConfig: IpsecVpnConfig = { + addresses: [{ + address: { + address: '192.168.1.63', + }, + prefixLength: 1 + }], + isLegacy: true, + vpnName: 'ikev1_xauth_psk', + vpnId: this.ikev1XauthId, + vpnType: VpnTypeModel.TYPE_IPSEC_XAUTH_PSK, + saveLogin: true, + trustedApplications: [], + blockedApplications: [], + ipsecIdentifier: 'ike1', + ipsecPreSharedKey: '123456', + userName: 'test', + password: '123456', + }; + ikev2IpsecPskConfig: IpsecVpnConfig = { + addresses: [{ + address: { + address: '192.168.1.21', + }, + prefixLength: 1 + }], + isLegacy: true, + vpnName: 'ikev2_ipsec_psk', + vpnId: this.ikev2PskId, + vpnType: VpnTypeModel.TYPE_IKEV2_IPSEC_PSK, + saveLogin: true, + trustedApplications: ["1", "2"], + blockedApplications: [], + ipsecIdentifier: 'ike2psk', + ipsecPreSharedKey: '222222', + }; + ikev2IpsecRsaConfig: IpsecVpnConfig = { + addresses: [{ + address: { + address: '192.168.1.210', + }, + prefixLength: 1 + }], + isLegacy: true, + vpnName: 'ikev2_ipsec_rsa', + vpnId: this.ikev2RsaId, + vpnType: VpnTypeModel.TYPE_IKEV2_IPSEC_RSA, + saveLogin: true, + trustedApplications: [], + blockedApplications: [], + ipsecIdentifier: 'rsaike2', + userName: 'test', + password: '123456', + }; + + ikev2IpsecMschapConfig: IpsecVpnConfig = { + addresses: [{ + address: { + address: '192.168.1.86', + }, + prefixLength: 1 + }], + isLegacy: true, + vpnName: 'ikev2_ipsec_mschap_rsa', + vpnId: this.ike2MschapRsaId, + vpnType: VpnTypeModel.TYPE_IKEV2_IPSEC_MSCHAPv2, + saveLogin: true, + trustedApplications: [], + blockedApplications: [], + userName: 'mschap', + password: '555555', + }; + + l2tpPskConfig: IpsecVpnConfig = { + addresses: [{ + address: { + address: '192.168.1.11', + }, + prefixLength: 1 + }], + isLegacy: true, + vpnName: 'vpn_l2tp_ipsec_psk', + vpnId: this.l2tpPskId, + vpnType: VpnTypeModel.TYPE_L2TP_IPSEC_PSK, + saveLogin: true, + trustedApplications: [], + blockedApplications: [], + ipsecPreSharedKey: '333333', + ipsecIdentifier: 'l2tppsk', + userName: 'use', + password: '123456', + } + l2tpConfig: IpsecVpnConfig = { + addresses: [{ + address: { + address: '192.168.1.21', + }, + prefixLength: 1 + }], + isLegacy: true, + vpnName: 'vpn_l2tp', + vpnId: this.l2tpId, + vpnType: VpnTypeModel.TYPE_L2TP, + saveLogin: true, + trustedApplications: [], + blockedApplications: [], + ipsecPreSharedKey: '222222', + userName: 'use', + password: '123456', + } + + l2tpRsaConfig: IpsecVpnConfig = { + addresses: [{ + address: { + address: '192.168.1.201', + }, + prefixLength: 1 + }], + isLegacy: true, + vpnName: 'vpn_l2tp_2', + vpnId: this.l2tpId_2, + vpnType: VpnTypeModel.TYPE_L2TP_IPSEC_PSK, + saveLogin: true, + trustedApplications: [], + blockedApplications: [], + ipsecPreSharedKey: '123456', + userName: 'testuser', + password: 'Hoperun321', + } + + async onCreate(want: Want) { + this.vpnConnection = vpnExtension.createVpnConnection(this.context); + } + + async connectVpn(type: string) { + let config = await this.getVpnConfigByType(type); + try { + Logger.info(TAG + `start setup ::${JSON.stringify(config.trustedApplications)}`); + if (config.pkcs12FileData !== undefined) { + Logger.info(TAG + `start setup pkcs12FileData size ::` + config.pkcs12FileData.length); + this.printUint8ArrayWithOffset(config.pkcs12FileData); + } else { + Logger.info(TAG + `start setup pkcs12FileData is undefined::`); + } + + this.vpnConnection?.create(config); + } catch (err) { + Logger.error(TAG + `setUp error = ${JSON.stringify(err)}`); + } + } + + disConnectVpn(type: string) { + this.vpnConnection = vpnExtension.createVpnConnection(this.context); + try { + Logger.info(TAG + `start destroy`); + this.vpnConnection.destroy(); + } catch (err) { + Logger.error(TAG + `destroy error = ${JSON.stringify(err)}`); + } + } + + getVpnIdByType(vpnType: string): string { + switch (vpnType) { + case 'ikev1_xauth_psk': + return this.ikev1XauthId; + case 'ikev2_ipsec_psk': + return this.ikev2PskId; + case 'ikev2_ipsec_rsa': + return this.ikev2RsaId; + case 'l2tp_ipsec_psk': + return this.l2tpPskId; + case 'l2tp': + return this.l2tpId; + case 'l2tp_2': + return this.l2tpId_2; + case 'l2tp_ipsec_rsa': + return this.l2tpId_3; + case 'ike2_mschap_rsa': + return this.ike2MschapRsaId; + default: + return this.ikev2PskId; + } + } + + async getVpnConfigByType(vpnType: string): Promise { + let id = await this.vpnConnection?.generateVpnId(); + Logger.info(TAG + `generateVpnId ::${JSON.stringify(id)}`); + switch (vpnType) { + case 'ikev2_ipsec_psk': + this.ikev2PskId = id ?? ''; + this.ikev2IpsecPskConfig.vpnId = this.ikev2PskId; + this.ikev2IpsecPskConfig.trustedApplications = this.trustedApplications; + if (this.serviceAddr !== "") { + this.ikev2IpsecPskConfig.addresses[0].address.address = this.serviceAddr; + } + if (this.routerAddr !== "" && this.ikev2IpsecPskConfig.routes) { + let a:vpn.RouteInfo = this.ikev2IpsecPskConfig.routes[0]; + a.destination.address.address = this.routerAddr; + a.destination.prefixLength = this.routerPrefixLength; + a.interface ="eth0"; + a.hasGateway = true; + a.isDefaultRoute =false; + a.gateway.address = this.routerAddr; + } + return this.ikev2IpsecPskConfig; + case 'ikev2_ipsec_rsa': + this.ikev2RsaId = id ?? ''; + this.ikev2IpsecRsaConfig.vpnId = this.ikev2RsaId; + this.ikev2IpsecRsaConfig.trustedApplications = this.trustedApplications; + this.ikev2IpsecRsaConfig.ipsecCaCertConfig = this.caUri; + this.ikev2IpsecRsaConfig.ipsecPublicUserCertConfig = this.userUri; + if (this.serviceAddr !== "") { + this.ikev2IpsecRsaConfig.addresses[0].address.address = this.serviceAddr; + } + this.ikev2IpsecRsaConfig.pkcs12FileData = this.pkcs12Uint8Array; + if (this.pkcs12Password != "") { + this.ikev2IpsecRsaConfig.pkcs12Password = this.pkcs12Password; + } else { + this.ikev2IpsecRsaConfig.pkcs12Password = "654321"; + } + return this.ikev2IpsecRsaConfig; + case 'l2tp_ipsec_psk': + this.l2tpPskId = id ?? ''; + this.l2tpPskConfig.vpnId = this.l2tpPskId; + this.l2tpPskConfig.trustedApplications = this.trustedApplications; + if (this.serviceAddr !== "") { + this.l2tpPskConfig.addresses[0].address.address = this.serviceAddr; + } + if (this.routerAddr !== "" && this.l2tpPskConfig.routes) { + let a:vpn.RouteInfo = this.l2tpPskConfig.routes[0]; + a.destination.address.address = this.routerAddr; + a.destination.prefixLength = this.routerPrefixLength; + a.interface ="eth0"; + a.hasGateway = true; + a.isDefaultRoute =false; + a.gateway.address = this.routerAddr; + } + return this.l2tpPskConfig; + case 'l2tp': + this.l2tpId = id ?? ''; + this.l2tpConfig.vpnId = this.l2tpId; + this.l2tpConfig.trustedApplications = this.trustedApplications; + if (this.serviceAddr !== "") { + this.l2tpConfig.addresses[0].address.address = this.serviceAddr; + } + if (this.routerAddr !== "" && this.l2tpConfig.routes) { + let a:vpn.RouteInfo = this.l2tpConfig.routes[0]; + a.destination.address.address = this.routerAddr; + a.destination.prefixLength = this.routerPrefixLength; + a.interface ="eth0"; + a.hasGateway = true; + a.isDefaultRoute =false; + a.gateway.address = this.routerAddr; + } + return this.l2tpConfig; + case 'l2tp_ipsec_rsa': + this.l2tpId_2 = id ?? ''; + this.l2tpRsaConfig.vpnId = this.l2tpId_2; + if (this.serviceAddr !== "") { + this.l2tpRsaConfig.addresses[0].address.address = this.serviceAddr; + } + if (this.routerAddr !== "" && this.l2tpRsaConfig.routes) { + let a:vpn.RouteInfo = this.l2tpRsaConfig.routes[0]; + a.destination.address.address = this.routerAddr; + a.destination.prefixLength = this.routerPrefixLength; + a.interface ="eth0"; + a.hasGateway = true; + a.isDefaultRoute =false; + a.gateway.address = this.routerAddr; + } + return this.l2tpRsaConfig; + case 'ike2_mschap_rsa': + this.ike2MschapRsaId = id ?? ''; + this.ikev2IpsecMschapConfig.vpnId = this.ike2MschapRsaId; + this.ikev2IpsecMschapConfig.trustedApplications = this.trustedApplications; + this.ikev2IpsecMschapConfig.ipsecCaCertConfig = this.caUri; + this.ikev2IpsecMschapConfig.ipsecPublicUserCertConfig = this.userUri; + if (this.serviceAddr !== "") { + this.ikev2IpsecMschapConfig.addresses[0].address.address = this.serviceAddr; + } + this.ikev2IpsecMschapConfig.pkcs12FileData = this.pkcs12Uint8Array; + if (this.pkcs12Password != "") { + this.ikev2IpsecMschapConfig.pkcs12Password = this.pkcs12Password; + } else { + this.ikev2IpsecMschapConfig.pkcs12Password = "123456"; + } + return this.ikev2IpsecMschapConfig; + default: + this.ikev2PskId = id ?? ''; + this.ikev2IpsecPskConfig.vpnId = this.ikev2PskId; + this.ikev2IpsecPskConfig.trustedApplications = this.trustedApplications; + return this.ikev2IpsecPskConfig; + } + } + + getFileDataFromUri(uri: string, callback: Function): void { + this.getMediaFileData(uri, (data: Uint8Array) => { + callback(data); + }); + } + + getMediaFileData(mediaUri: string, callback: Function): void { + Logger.info(TAG + 'FA getMediaFile start'); + let file: fs.File | undefined = undefined; + try { + file = fs.openSync(mediaUri, fs.OpenMode.READ_ONLY); + let stat = fs.statSync(file.fd); + let buf = new ArrayBuffer(Number(stat.size)); + let num = fs.readSync(file.fd, buf); + Logger.info(TAG + 'FA getMediaFile success'); + callback(new Uint8Array(buf)); + } catch (err) { + let e: BusinessError = err as BusinessError; + Logger.info(TAG + 'FA getMediaFileData failed with err, message: ' + e.message + ', code: ' + e.code); + callback(undefined); + } finally { + try { + if (file !== undefined && file !== null) { + fs.closeSync(file.fd); + } + } catch (err) { + let e: BusinessError = err as BusinessError; + Logger.info(TAG + 'FA close io stream failed with err, message: ' + e.message + ', code: ' + e.code); + } + } + } + + printUint8ArrayWithOffset(u8: Uint8Array): void { + let rowSize = 16; + + for (let i = 0; i < u8.length; i += rowSize) { + let row = u8.subarray(i, i + rowSize); + let offsetStr = `0x${i.toString(16).padStart(8, '0')}`; + let decimalStr = Array.from(row) + .map(byte => byte.toString(16).padStart(2, '0')) + .join(' '); + + Logger.info(TAG + `${offsetStr} ${decimalStr}`); + } + } + + onRequest(want: Want, startId: number) { + Logger.info(TAG + `onRequest, want: ${JSON.stringify(want)}`); + if (want.parameters) { + if (want.parameters['connect']) { + let type = want.parameters['connect'] as string; + let caUri = want.parameters['caCertUri'] as string; + let userUri = want.parameters['userCertUri'] as string; + let whiteList = want.parameters['trustedApplications'] as string; + this.serviceAddr = want.parameters['serviceAddr'] as string; + this.routerAddr = want.parameters['routerAddr'] as string; + this.routerPrefixLength = want.parameters['routerPrefixLength'] as number; + let uri = want.parameters['pkcs12FilePath'] as string + if (uri.length != 0) { + this.getFileDataFromUri(uri, (data: Uint8Array) => { + // this.printUint8ArrayWithOffset(data); + this.pkcs12Uint8Array = data; + Logger.info(TAG + `start setup pkcs12FileData size ::` + this.pkcs12Uint8Array.length); + }); + } + this.pkcs12Password = want.parameters['pkcs12Password'] as string; + + if (whiteList) { + this.trustedApplications = whiteList.split(','); + } + this.connectVpn(type); + } + if (want.parameters['disconnect']) { + let type = want.parameters['disconnect'] as string; + this.disConnectVpn(type); + } + } + } + + onConnect(want: Want) { + Logger.info(TAG + `onConnect, want: ${want.abilityName}`); + } + + onDisconnect(want: Want) { + Logger.info(TAG + `onDisconnect, want: ${want.abilityName}`); + } + + onDestroy() { + Logger.info(TAG + `onDestroy`); + } +} \ No newline at end of file diff --git a/code/DocsSample/NetWork_Kit/NetWorkKit_NetManager/VPNControl_Case/entry/src/main/module.json5 b/code/DocsSample/NetWork_Kit/NetWorkKit_NetManager/VPNControl_Case/entry/src/main/module.json5 index c435627e6c355c920f622b8e7dd21030fd523b48..1bea6865a3b15340923dc0c4f9b577f2ad9097c7 100644 --- a/code/DocsSample/NetWork_Kit/NetWorkKit_NetManager/VPNControl_Case/entry/src/main/module.json5 +++ b/code/DocsSample/NetWork_Kit/NetWorkKit_NetManager/VPNControl_Case/entry/src/main/module.json5 @@ -66,8 +66,13 @@ "name": "MyVpnExtAbility", "srcEntry": "./ets/vpnability/VPNExtentionAbility.ets", "type": "vpn" - } + }, // [End create_vpn_extension_ability] + { + "name": "SysVpnExtAbility", + "srcEntry": "./ets/vpnability/SysVpnExtAbility.ets", + "type": "vpn" + } ], "requestPermissions": [ { diff --git a/code/DocsSample/NetWork_Kit/NetWorkKit_NetManager/VPNControl_Case/entry/src/main/resources/base/profile/main_pages.json b/code/DocsSample/NetWork_Kit/NetWorkKit_NetManager/VPNControl_Case/entry/src/main/resources/base/profile/main_pages.json index 65b5903f306bc5bdf078467edf3e8916ed626ccc..6f0764dd10cb258120014ef84cdc7302d6762f38 100644 --- a/code/DocsSample/NetWork_Kit/NetWorkKit_NetManager/VPNControl_Case/entry/src/main/resources/base/profile/main_pages.json +++ b/code/DocsSample/NetWork_Kit/NetWorkKit_NetManager/VPNControl_Case/entry/src/main/resources/base/profile/main_pages.json @@ -2,6 +2,7 @@ "src": [ "pages/Index", "pages/StartVpn", - "pages/StopVpn" + "pages/StopVpn", + "pages/SysVpn" ] }