1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.internal.net; 18 19 import android.annotation.UnsupportedAppUsage; 20 import android.os.Build; 21 import android.net.ProxyInfo; 22 import android.net.Uri; 23 import android.os.Parcel; 24 import android.os.Parcelable; 25 import android.text.TextUtils; 26 27 import java.net.InetAddress; 28 import java.nio.charset.StandardCharsets; 29 30 /** 31 * Parcel-like entity class for VPN profiles. To keep things simple, all 32 * fields are package private. Methods are provided for serialization, so 33 * storage can be implemented easily. Two rules are set for this class. 34 * First, all fields must be kept non-null. Second, always make a copy 35 * using clone() before modifying. 36 * 37 * @hide 38 */ 39 public class VpnProfile implements Cloneable, Parcelable { 40 private static final String TAG = "VpnProfile"; 41 42 // Match these constants with R.array.vpn_types. 43 public static final int TYPE_PPTP = 0; 44 public static final int TYPE_L2TP_IPSEC_PSK = 1; 45 public static final int TYPE_L2TP_IPSEC_RSA = 2; 46 public static final int TYPE_IPSEC_XAUTH_PSK = 3; 47 public static final int TYPE_IPSEC_XAUTH_RSA = 4; 48 public static final int TYPE_IPSEC_HYBRID_RSA = 5; 49 public static final int TYPE_MAX = 5; 50 51 // Match these constants with R.array.vpn_proxy_settings. 52 public static final int PROXY_NONE = 0; 53 public static final int PROXY_MANUAL = 1; 54 55 // Entity fields. 56 @UnsupportedAppUsage 57 public final String key; // -1 58 @UnsupportedAppUsage 59 public String name = ""; // 0 60 @UnsupportedAppUsage 61 public int type = TYPE_PPTP; // 1 62 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) 63 public String server = ""; // 2 64 @UnsupportedAppUsage 65 public String username = ""; // 3 66 public String password = ""; // 4 67 public String dnsServers = ""; // 5 68 public String searchDomains = ""; // 6 69 public String routes = ""; // 7 70 public boolean mppe = true; // 8 71 public String l2tpSecret = ""; // 9 72 public String ipsecIdentifier = "";// 10 73 public String ipsecSecret = ""; // 11 74 public String ipsecUserCert = ""; // 12 75 public String ipsecCaCert = ""; // 13 76 public String ipsecServerCert = "";// 14 77 public ProxyInfo proxy = null; // 15~18 78 79 // Helper fields. 80 @UnsupportedAppUsage 81 public boolean saveLogin = false; 82 VpnProfile(String key)83 public VpnProfile(String key) { 84 this.key = key; 85 } 86 87 @UnsupportedAppUsage VpnProfile(Parcel in)88 public VpnProfile(Parcel in) { 89 key = in.readString(); 90 name = in.readString(); 91 type = in.readInt(); 92 server = in.readString(); 93 username = in.readString(); 94 password = in.readString(); 95 dnsServers = in.readString(); 96 searchDomains = in.readString(); 97 routes = in.readString(); 98 mppe = in.readInt() != 0; 99 l2tpSecret = in.readString(); 100 ipsecIdentifier = in.readString(); 101 ipsecSecret = in.readString(); 102 ipsecUserCert = in.readString(); 103 ipsecCaCert = in.readString(); 104 ipsecServerCert = in.readString(); 105 saveLogin = in.readInt() != 0; 106 proxy = in.readParcelable(null); 107 } 108 109 @Override writeToParcel(Parcel out, int flags)110 public void writeToParcel(Parcel out, int flags) { 111 out.writeString(key); 112 out.writeString(name); 113 out.writeInt(type); 114 out.writeString(server); 115 out.writeString(username); 116 out.writeString(password); 117 out.writeString(dnsServers); 118 out.writeString(searchDomains); 119 out.writeString(routes); 120 out.writeInt(mppe ? 1 : 0); 121 out.writeString(l2tpSecret); 122 out.writeString(ipsecIdentifier); 123 out.writeString(ipsecSecret); 124 out.writeString(ipsecUserCert); 125 out.writeString(ipsecCaCert); 126 out.writeString(ipsecServerCert); 127 out.writeInt(saveLogin ? 1 : 0); 128 out.writeParcelable(proxy, flags); 129 } 130 131 @UnsupportedAppUsage decode(String key, byte[] value)132 public static VpnProfile decode(String key, byte[] value) { 133 try { 134 if (key == null) { 135 return null; 136 } 137 138 String[] values = new String(value, StandardCharsets.UTF_8).split("\0", -1); 139 // There can be 14 - 19 Bytes in values.length. 140 if (values.length < 14 || values.length > 19) { 141 return null; 142 } 143 144 VpnProfile profile = new VpnProfile(key); 145 profile.name = values[0]; 146 profile.type = Integer.parseInt(values[1]); 147 if (profile.type < 0 || profile.type > TYPE_MAX) { 148 return null; 149 } 150 profile.server = values[2]; 151 profile.username = values[3]; 152 profile.password = values[4]; 153 profile.dnsServers = values[5]; 154 profile.searchDomains = values[6]; 155 profile.routes = values[7]; 156 profile.mppe = Boolean.parseBoolean(values[8]); 157 profile.l2tpSecret = values[9]; 158 profile.ipsecIdentifier = values[10]; 159 profile.ipsecSecret = values[11]; 160 profile.ipsecUserCert = values[12]; 161 profile.ipsecCaCert = values[13]; 162 profile.ipsecServerCert = (values.length > 14) ? values[14] : ""; 163 if (values.length > 15) { 164 String host = (values.length > 15) ? values[15] : ""; 165 String port = (values.length > 16) ? values[16] : ""; 166 String exclList = (values.length > 17) ? values[17] : ""; 167 String pacFileUrl = (values.length > 18) ? values[18] : ""; 168 if (pacFileUrl.isEmpty()) { 169 profile.proxy = new ProxyInfo(host, port.isEmpty() ? 170 0 : Integer.parseInt(port), exclList); 171 } else { 172 profile.proxy = new ProxyInfo(pacFileUrl); 173 } 174 } // else profle.proxy = null 175 profile.saveLogin = !profile.username.isEmpty() || !profile.password.isEmpty(); 176 return profile; 177 } catch (Exception e) { 178 // ignore 179 } 180 return null; 181 } 182 encode()183 public byte[] encode() { 184 StringBuilder builder = new StringBuilder(name); 185 builder.append('\0').append(type); 186 builder.append('\0').append(server); 187 builder.append('\0').append(saveLogin ? username : ""); 188 builder.append('\0').append(saveLogin ? password : ""); 189 builder.append('\0').append(dnsServers); 190 builder.append('\0').append(searchDomains); 191 builder.append('\0').append(routes); 192 builder.append('\0').append(mppe); 193 builder.append('\0').append(l2tpSecret); 194 builder.append('\0').append(ipsecIdentifier); 195 builder.append('\0').append(ipsecSecret); 196 builder.append('\0').append(ipsecUserCert); 197 builder.append('\0').append(ipsecCaCert); 198 builder.append('\0').append(ipsecServerCert); 199 if (proxy != null) { 200 builder.append('\0').append(proxy.getHost() != null ? proxy.getHost() : ""); 201 builder.append('\0').append(proxy.getPort()); 202 builder.append('\0').append(proxy.getExclusionListAsString() != null ? 203 proxy.getExclusionListAsString() : ""); 204 builder.append('\0').append(proxy.getPacFileUrl().toString()); 205 } 206 return builder.toString().getBytes(StandardCharsets.UTF_8); 207 } 208 209 /** 210 * Tests if profile is valid for lockdown, which requires IPv4 address for 211 * both server and DNS. Server hostnames would require using DNS before 212 * connection. 213 */ isValidLockdownProfile()214 public boolean isValidLockdownProfile() { 215 return isTypeValidForLockdown() 216 && isServerAddressNumeric() 217 && hasDns() 218 && areDnsAddressesNumeric(); 219 } 220 221 /** Returns {@code true} if the VPN type is valid for lockdown. */ isTypeValidForLockdown()222 public boolean isTypeValidForLockdown() { 223 // b/7064069: lockdown firewall blocks ports used for PPTP 224 return type != TYPE_PPTP; 225 } 226 227 /** Returns {@code true} if the server address is numeric, e.g. 8.8.8.8 */ isServerAddressNumeric()228 public boolean isServerAddressNumeric() { 229 try { 230 InetAddress.parseNumericAddress(server); 231 } catch (IllegalArgumentException e) { 232 return false; 233 } 234 return true; 235 } 236 237 /** Returns {@code true} if one or more DNS servers are specified. */ hasDns()238 public boolean hasDns() { 239 return !TextUtils.isEmpty(dnsServers); 240 } 241 242 /** 243 * Returns {@code true} if all DNS servers have numeric addresses, 244 * e.g. 8.8.8.8 245 */ areDnsAddressesNumeric()246 public boolean areDnsAddressesNumeric() { 247 try { 248 for (String dnsServer : dnsServers.split(" +")) { 249 InetAddress.parseNumericAddress(dnsServer); 250 } 251 } catch (IllegalArgumentException e) { 252 return false; 253 } 254 return true; 255 } 256 257 public static final Creator<VpnProfile> CREATOR = new Creator<VpnProfile>() { 258 @Override 259 public VpnProfile createFromParcel(Parcel in) { 260 return new VpnProfile(in); 261 } 262 263 @Override 264 public VpnProfile[] newArray(int size) { 265 return new VpnProfile[size]; 266 } 267 }; 268 269 @Override describeContents()270 public int describeContents() { 271 return 0; 272 } 273 } 274