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