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.NonNull; 20 import android.compat.annotation.UnsupportedAppUsage; 21 import android.net.Ikev2VpnProfile; 22 import android.net.PlatformVpnProfile; 23 import android.net.ProxyInfo; 24 import android.net.Uri; 25 import android.os.Build; 26 import android.os.Parcel; 27 import android.os.Parcelable; 28 import android.text.TextUtils; 29 30 import com.android.internal.annotations.VisibleForTesting; 31 import com.android.net.module.util.ProxyUtils; 32 33 import java.io.UnsupportedEncodingException; 34 import java.net.InetAddress; 35 import java.net.URLDecoder; 36 import java.net.URLEncoder; 37 import java.nio.charset.StandardCharsets; 38 import java.util.ArrayList; 39 import java.util.Arrays; 40 import java.util.Collections; 41 import java.util.List; 42 import java.util.Objects; 43 44 /** 45 * Profile storage class for a platform VPN. 46 * 47 * <p>This class supports both the Legacy VPN, as well as application-configurable platform VPNs 48 * (such as IKEv2/IPsec). 49 * 50 * <p>This class is serialized and deserialized via the {@link #encode()} and {@link #decode()} 51 * functions for persistent storage in the Android Keystore. The encoding is entirely custom, but 52 * must be kept for backward compatibility for devices upgrading between Android versions. 53 * 54 * @hide 55 */ 56 public final class VpnProfile implements Cloneable, Parcelable { 57 private static final String TAG = "VpnProfile"; 58 59 @VisibleForTesting static final String VALUE_DELIMITER = "\0"; 60 @VisibleForTesting static final String LIST_DELIMITER = ","; 61 62 // Match these constants with R.array.vpn_types. 63 public static final int TYPE_PPTP = 0; 64 public static final int TYPE_L2TP_IPSEC_PSK = 1; 65 public static final int TYPE_L2TP_IPSEC_RSA = 2; 66 public static final int TYPE_IPSEC_XAUTH_PSK = 3; 67 public static final int TYPE_IPSEC_XAUTH_RSA = 4; 68 public static final int TYPE_IPSEC_HYBRID_RSA = 5; 69 public static final int TYPE_IKEV2_IPSEC_USER_PASS = 6; 70 public static final int TYPE_IKEV2_IPSEC_PSK = 7; 71 public static final int TYPE_IKEV2_IPSEC_RSA = 8; 72 public static final int TYPE_MAX = 8; 73 74 // Match these constants with R.array.vpn_proxy_settings. 75 public static final int PROXY_NONE = 0; 76 public static final int PROXY_MANUAL = 1; 77 78 private static final String ENCODED_NULL_PROXY_INFO = "\0\0\0\0"; 79 80 /** Default URL encoding. */ 81 private static final String DEFAULT_ENCODING = StandardCharsets.UTF_8.name(); 82 83 // Entity fields. 84 @UnsupportedAppUsage 85 public final String key; // -1 86 87 @UnsupportedAppUsage 88 public String name = ""; // 0 89 90 @UnsupportedAppUsage 91 public int type = TYPE_PPTP; // 1 92 93 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) 94 public String server = ""; // 2 95 96 @UnsupportedAppUsage 97 public String username = ""; // 3 98 public String password = ""; // 4 99 public String dnsServers = ""; // 5 100 public String searchDomains = ""; // 6 101 public String routes = ""; // 7 102 public boolean mppe = true; // 8 103 public String l2tpSecret = ""; // 9 104 public String ipsecIdentifier = ""; // 10 105 106 /** 107 * The RSA private key or pre-shared key used for authentication. 108 * 109 * <p>If areAuthParamsInline is {@code true}, this String will be either: 110 * 111 * <ul> 112 * <li>If this is an IKEv2 RSA profile: a PKCS#8 encoded {@link java.security.PrivateKey} 113 * <li>If this is an IKEv2 PSK profile: a string value representing the PSK. 114 * </ul> 115 */ 116 public String ipsecSecret = ""; // 11 117 118 /** 119 * The RSA certificate to be used for digital signature authentication. 120 * 121 * <p>If areAuthParamsInline is {@code true}, this String will be a pem-encoded {@link 122 * java.security.X509Certificate} 123 */ 124 public String ipsecUserCert = ""; // 12 125 126 /** 127 * The RSA certificate that should be used to verify the server's end/target certificate. 128 * 129 * <p>If areAuthParamsInline is {@code true}, this String will be a pem-encoded {@link 130 * java.security.X509Certificate} 131 */ 132 public String ipsecCaCert = ""; // 13 133 public String ipsecServerCert = ""; // 14 134 public ProxyInfo proxy = null; // 15~18 135 136 /** 137 * The list of allowable algorithms. 138 */ 139 private List<String> mAllowedAlgorithms = new ArrayList<>(); // 19 140 public boolean isBypassable = false; // 20 141 public boolean isMetered = false; // 21 142 public int maxMtu = PlatformVpnProfile.MAX_MTU_DEFAULT; // 22 143 public boolean areAuthParamsInline = false; // 23 144 public final boolean isRestrictedToTestNetworks; // 24 145 146 // Helper fields. 147 @UnsupportedAppUsage 148 public transient boolean saveLogin = false; 149 VpnProfile(String key)150 public VpnProfile(String key) { 151 this(key, false); 152 } 153 VpnProfile(String key, boolean isRestrictedToTestNetworks)154 public VpnProfile(String key, boolean isRestrictedToTestNetworks) { 155 this.key = key; 156 this.isRestrictedToTestNetworks = isRestrictedToTestNetworks; 157 } 158 159 @UnsupportedAppUsage VpnProfile(Parcel in)160 public VpnProfile(Parcel in) { 161 key = in.readString(); 162 name = in.readString(); 163 type = in.readInt(); 164 server = in.readString(); 165 username = in.readString(); 166 password = in.readString(); 167 dnsServers = in.readString(); 168 searchDomains = in.readString(); 169 routes = in.readString(); 170 mppe = in.readInt() != 0; 171 l2tpSecret = in.readString(); 172 ipsecIdentifier = in.readString(); 173 ipsecSecret = in.readString(); 174 ipsecUserCert = in.readString(); 175 ipsecCaCert = in.readString(); 176 ipsecServerCert = in.readString(); 177 saveLogin = in.readInt() != 0; 178 proxy = in.readParcelable(null); 179 mAllowedAlgorithms = new ArrayList<>(); 180 in.readList(mAllowedAlgorithms, null); 181 isBypassable = in.readBoolean(); 182 isMetered = in.readBoolean(); 183 maxMtu = in.readInt(); 184 areAuthParamsInline = in.readBoolean(); 185 isRestrictedToTestNetworks = in.readBoolean(); 186 } 187 188 /** 189 * Retrieves the list of allowed algorithms. 190 * 191 * <p>The contained elements are as listed in {@link IpSecAlgorithm} 192 */ getAllowedAlgorithms()193 public List<String> getAllowedAlgorithms() { 194 return Collections.unmodifiableList(mAllowedAlgorithms); 195 } 196 197 /** 198 * Validates and sets the list of algorithms that can be used for the IPsec transforms. 199 * 200 * @param allowedAlgorithms the list of allowable algorithms, as listed in {@link 201 * IpSecAlgorithm}. 202 */ setAllowedAlgorithms(List<String> allowedAlgorithms)203 public void setAllowedAlgorithms(List<String> allowedAlgorithms) { 204 mAllowedAlgorithms = allowedAlgorithms; 205 } 206 207 @Override writeToParcel(Parcel out, int flags)208 public void writeToParcel(Parcel out, int flags) { 209 out.writeString(key); 210 out.writeString(name); 211 out.writeInt(type); 212 out.writeString(server); 213 out.writeString(username); 214 out.writeString(password); 215 out.writeString(dnsServers); 216 out.writeString(searchDomains); 217 out.writeString(routes); 218 out.writeInt(mppe ? 1 : 0); 219 out.writeString(l2tpSecret); 220 out.writeString(ipsecIdentifier); 221 out.writeString(ipsecSecret); 222 out.writeString(ipsecUserCert); 223 out.writeString(ipsecCaCert); 224 out.writeString(ipsecServerCert); 225 out.writeInt(saveLogin ? 1 : 0); 226 out.writeParcelable(proxy, flags); 227 out.writeList(mAllowedAlgorithms); 228 out.writeBoolean(isBypassable); 229 out.writeBoolean(isMetered); 230 out.writeInt(maxMtu); 231 out.writeBoolean(areAuthParamsInline); 232 out.writeBoolean(isRestrictedToTestNetworks); 233 } 234 235 /** 236 * Decodes a VpnProfile instance from the encoded byte array. 237 * 238 * <p>See {@link #encode()} 239 */ 240 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) decode(String key, byte[] value)241 public static VpnProfile decode(String key, byte[] value) { 242 try { 243 if (key == null) { 244 return null; 245 } 246 247 String[] values = new String(value, StandardCharsets.UTF_8).split(VALUE_DELIMITER, -1); 248 // Acceptable numbers of values are: 249 // 14-19: Standard profile, with option for serverCert, proxy 250 // 24: Standard profile with serverCert, proxy and platform-VPN parameters 251 // 25: Standard profile with platform-VPN parameters and isRestrictedToTestNetworks 252 if ((values.length < 14 || values.length > 19) 253 && values.length != 24 && values.length != 25) { 254 return null; 255 } 256 257 final boolean isRestrictedToTestNetworks; 258 if (values.length >= 25) { 259 isRestrictedToTestNetworks = Boolean.parseBoolean(values[24]); 260 } else { 261 isRestrictedToTestNetworks = false; 262 } 263 264 VpnProfile profile = new VpnProfile(key, isRestrictedToTestNetworks); 265 profile.name = values[0]; 266 profile.type = Integer.parseInt(values[1]); 267 if (profile.type < 0 || profile.type > TYPE_MAX) { 268 return null; 269 } 270 profile.server = values[2]; 271 profile.username = values[3]; 272 profile.password = values[4]; 273 profile.dnsServers = values[5]; 274 profile.searchDomains = values[6]; 275 profile.routes = values[7]; 276 profile.mppe = Boolean.parseBoolean(values[8]); 277 profile.l2tpSecret = values[9]; 278 profile.ipsecIdentifier = values[10]; 279 profile.ipsecSecret = values[11]; 280 profile.ipsecUserCert = values[12]; 281 profile.ipsecCaCert = values[13]; 282 profile.ipsecServerCert = (values.length > 14) ? values[14] : ""; 283 if (values.length > 15) { 284 String host = (values.length > 15) ? values[15] : ""; 285 String port = (values.length > 16) ? values[16] : ""; 286 String exclList = (values.length > 17) ? values[17] : ""; 287 String pacFileUrl = (values.length > 18) ? values[18] : ""; 288 if (!host.isEmpty() || !port.isEmpty() || !exclList.isEmpty()) { 289 profile.proxy = 290 ProxyInfo.buildDirectProxy(host, port.isEmpty() ? 291 0 : Integer.parseInt(port), 292 ProxyUtils.exclusionStringAsList(exclList)); 293 } else if (!pacFileUrl.isEmpty()) { 294 profile.proxy = ProxyInfo.buildPacProxy(Uri.parse(pacFileUrl)); 295 } 296 } // else profile.proxy = null 297 298 // Either all must be present, or none must be. 299 if (values.length >= 24) { 300 profile.mAllowedAlgorithms = new ArrayList<>(); 301 for (String algo : Arrays.asList(values[19].split(LIST_DELIMITER))) { 302 profile.mAllowedAlgorithms.add(URLDecoder.decode(algo, DEFAULT_ENCODING)); 303 } 304 305 profile.isBypassable = Boolean.parseBoolean(values[20]); 306 profile.isMetered = Boolean.parseBoolean(values[21]); 307 profile.maxMtu = Integer.parseInt(values[22]); 308 profile.areAuthParamsInline = Boolean.parseBoolean(values[23]); 309 } 310 311 // isRestrictedToTestNetworks (values[24]) assigned as part of the constructor 312 313 profile.saveLogin = !profile.username.isEmpty() || !profile.password.isEmpty(); 314 return profile; 315 } catch (Exception e) { 316 // ignore 317 } 318 return null; 319 } 320 321 /** 322 * Encodes a VpnProfile instance to a byte array for storage. 323 * 324 * <p>See {@link #decode(String, byte[])} 325 */ encode()326 public byte[] encode() { 327 StringBuilder builder = new StringBuilder(name); 328 builder.append(VALUE_DELIMITER).append(type); 329 builder.append(VALUE_DELIMITER).append(server); 330 builder.append(VALUE_DELIMITER).append(saveLogin ? username : ""); 331 builder.append(VALUE_DELIMITER).append(saveLogin ? password : ""); 332 builder.append(VALUE_DELIMITER).append(dnsServers); 333 builder.append(VALUE_DELIMITER).append(searchDomains); 334 builder.append(VALUE_DELIMITER).append(routes); 335 builder.append(VALUE_DELIMITER).append(mppe); 336 builder.append(VALUE_DELIMITER).append(l2tpSecret); 337 builder.append(VALUE_DELIMITER).append(ipsecIdentifier); 338 builder.append(VALUE_DELIMITER).append(ipsecSecret); 339 builder.append(VALUE_DELIMITER).append(ipsecUserCert); 340 builder.append(VALUE_DELIMITER).append(ipsecCaCert); 341 builder.append(VALUE_DELIMITER).append(ipsecServerCert); 342 if (proxy != null) { 343 builder.append(VALUE_DELIMITER).append(proxy.getHost() != null ? proxy.getHost() : ""); 344 builder.append(VALUE_DELIMITER).append(proxy.getPort()); 345 builder.append(VALUE_DELIMITER) 346 .append( 347 ProxyUtils.exclusionListAsString(proxy.getExclusionList()) != null 348 ? ProxyUtils.exclusionListAsString(proxy.getExclusionList()) 349 : ""); 350 builder.append(VALUE_DELIMITER).append(proxy.getPacFileUrl().toString()); 351 } else { 352 builder.append(ENCODED_NULL_PROXY_INFO); 353 } 354 355 final List<String> encodedAlgoNames = new ArrayList<>(); 356 357 try { 358 for (String algo : mAllowedAlgorithms) { 359 encodedAlgoNames.add(URLEncoder.encode(algo, DEFAULT_ENCODING)); 360 } 361 } catch (UnsupportedEncodingException e) { 362 // Unexpected error 363 throw new IllegalStateException("Failed to encode algorithms.", e); 364 } 365 366 builder.append(VALUE_DELIMITER).append(String.join(LIST_DELIMITER, encodedAlgoNames)); 367 368 builder.append(VALUE_DELIMITER).append(isBypassable); 369 builder.append(VALUE_DELIMITER).append(isMetered); 370 builder.append(VALUE_DELIMITER).append(maxMtu); 371 builder.append(VALUE_DELIMITER).append(areAuthParamsInline); 372 builder.append(VALUE_DELIMITER).append(isRestrictedToTestNetworks); 373 374 return builder.toString().getBytes(StandardCharsets.UTF_8); 375 } 376 377 /** Checks if this profile specifies a LegacyVpn type. */ isLegacyType(int type)378 public static boolean isLegacyType(int type) { 379 switch (type) { 380 case VpnProfile.TYPE_PPTP: 381 case VpnProfile.TYPE_L2TP_IPSEC_PSK: 382 case VpnProfile.TYPE_L2TP_IPSEC_RSA: 383 case VpnProfile.TYPE_IPSEC_XAUTH_PSK: 384 case VpnProfile.TYPE_IPSEC_XAUTH_RSA: 385 case VpnProfile.TYPE_IPSEC_HYBRID_RSA: 386 return true; 387 default: 388 return false; 389 } 390 } 391 isValidLockdownLegacyVpnProfile()392 private boolean isValidLockdownLegacyVpnProfile() { 393 return isLegacyType(type) && isServerAddressNumeric() && hasDns() 394 && areDnsAddressesNumeric(); 395 } 396 isValidLockdownPlatformVpnProfile()397 private boolean isValidLockdownPlatformVpnProfile() { 398 return Ikev2VpnProfile.isValidVpnProfile(this); 399 } 400 401 /** 402 * Tests if profile is valid for lockdown. 403 * 404 * <p>For LegacyVpn profiles, this requires an IPv4 address for both the server and DNS. 405 * 406 * <p>For PlatformVpn profiles, this requires a server, an identifier and the relevant fields to 407 * be non-null. 408 */ isValidLockdownProfile()409 public boolean isValidLockdownProfile() { 410 return isTypeValidForLockdown() 411 && (isValidLockdownLegacyVpnProfile() || isValidLockdownPlatformVpnProfile()); 412 } 413 414 /** Returns {@code true} if the VPN type is valid for lockdown. */ isTypeValidForLockdown()415 public boolean isTypeValidForLockdown() { 416 // b/7064069: lockdown firewall blocks ports used for PPTP 417 return type != TYPE_PPTP; 418 } 419 420 /** Returns {@code true} if the server address is numeric, e.g. 8.8.8.8 */ isServerAddressNumeric()421 public boolean isServerAddressNumeric() { 422 try { 423 InetAddress.parseNumericAddress(server); 424 } catch (IllegalArgumentException e) { 425 return false; 426 } 427 return true; 428 } 429 430 /** Returns {@code true} if one or more DNS servers are specified. */ hasDns()431 public boolean hasDns() { 432 return !TextUtils.isEmpty(dnsServers); 433 } 434 435 /** Returns {@code true} if all DNS servers have numeric addresses, e.g. 8.8.8.8 */ areDnsAddressesNumeric()436 public boolean areDnsAddressesNumeric() { 437 try { 438 for (String dnsServer : dnsServers.split(" +")) { 439 InetAddress.parseNumericAddress(dnsServer); 440 } 441 } catch (IllegalArgumentException e) { 442 return false; 443 } 444 return true; 445 } 446 447 /** Generates a hashcode over the VpnProfile. */ 448 @Override hashCode()449 public int hashCode() { 450 return Objects.hash( 451 key, type, server, username, password, dnsServers, searchDomains, routes, mppe, 452 l2tpSecret, ipsecIdentifier, ipsecSecret, ipsecUserCert, ipsecCaCert, ipsecServerCert, 453 proxy, mAllowedAlgorithms, isBypassable, isMetered, maxMtu, areAuthParamsInline, 454 isRestrictedToTestNetworks); 455 } 456 457 /** Checks VPN profiles for interior equality. */ 458 @Override equals(Object obj)459 public boolean equals(Object obj) { 460 if (!(obj instanceof VpnProfile)) { 461 return false; 462 } 463 464 final VpnProfile other = (VpnProfile) obj; 465 return Objects.equals(key, other.key) 466 && Objects.equals(name, other.name) 467 && type == other.type 468 && Objects.equals(server, other.server) 469 && Objects.equals(username, other.username) 470 && Objects.equals(password, other.password) 471 && Objects.equals(dnsServers, other.dnsServers) 472 && Objects.equals(searchDomains, other.searchDomains) 473 && Objects.equals(routes, other.routes) 474 && mppe == other.mppe 475 && Objects.equals(l2tpSecret, other.l2tpSecret) 476 && Objects.equals(ipsecIdentifier, other.ipsecIdentifier) 477 && Objects.equals(ipsecSecret, other.ipsecSecret) 478 && Objects.equals(ipsecUserCert, other.ipsecUserCert) 479 && Objects.equals(ipsecCaCert, other.ipsecCaCert) 480 && Objects.equals(ipsecServerCert, other.ipsecServerCert) 481 && Objects.equals(proxy, other.proxy) 482 && Objects.equals(mAllowedAlgorithms, other.mAllowedAlgorithms) 483 && isBypassable == other.isBypassable 484 && isMetered == other.isMetered 485 && maxMtu == other.maxMtu 486 && areAuthParamsInline == other.areAuthParamsInline 487 && isRestrictedToTestNetworks == other.isRestrictedToTestNetworks; 488 } 489 490 @NonNull 491 public static final Creator<VpnProfile> CREATOR = new Creator<VpnProfile>() { 492 @Override 493 public VpnProfile createFromParcel(Parcel in) { 494 return new VpnProfile(in); 495 } 496 497 @Override 498 public VpnProfile[] newArray(int size) { 499 return new VpnProfile[size]; 500 } 501 }; 502 503 @Override describeContents()504 public int describeContents() { 505 return 0; 506 } 507 } 508