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