1 /* 2 * Copyright (C) 2019 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 android.net; 18 19 import static android.net.PlatformVpnProfile.TYPE_IKEV2_IPSEC_PSK; 20 import static android.net.PlatformVpnProfile.TYPE_IKEV2_IPSEC_RSA; 21 import static android.net.PlatformVpnProfile.TYPE_IKEV2_IPSEC_USER_PASS; 22 23 import static com.android.internal.annotations.VisibleForTesting.Visibility; 24 import static com.android.internal.util.Preconditions.checkStringNotEmpty; 25 26 import android.annotation.NonNull; 27 import android.annotation.Nullable; 28 import android.annotation.RequiresFeature; 29 import android.content.pm.PackageManager; 30 import android.os.Process; 31 import android.security.Credentials; 32 import android.security.KeyStore; 33 import android.security.keystore.AndroidKeyStoreProvider; 34 35 import com.android.internal.annotations.VisibleForTesting; 36 import com.android.internal.net.VpnProfile; 37 38 import java.io.IOException; 39 import java.nio.charset.StandardCharsets; 40 import java.security.GeneralSecurityException; 41 import java.security.KeyFactory; 42 import java.security.NoSuchAlgorithmException; 43 import java.security.PrivateKey; 44 import java.security.cert.CertificateEncodingException; 45 import java.security.cert.CertificateException; 46 import java.security.cert.X509Certificate; 47 import java.security.spec.InvalidKeySpecException; 48 import java.security.spec.PKCS8EncodedKeySpec; 49 import java.util.ArrayList; 50 import java.util.Arrays; 51 import java.util.Base64; 52 import java.util.Collections; 53 import java.util.List; 54 import java.util.Objects; 55 56 /** 57 * The Ikev2VpnProfile is a configuration for the platform setup of IKEv2/IPsec VPNs. 58 * 59 * <p>Together with VpnManager, this allows apps to provision IKEv2/IPsec VPNs that do not require 60 * the VPN app to constantly run in the background. 61 * 62 * @see VpnManager 63 * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3.2">RFC 7296 - Internet Key 64 * Exchange, Version 2 (IKEv2)</a> 65 */ 66 public final class Ikev2VpnProfile extends PlatformVpnProfile { 67 /** Prefix for when a Private Key is an alias to look for in KeyStore @hide */ 68 public static final String PREFIX_KEYSTORE_ALIAS = "KEYSTORE_ALIAS:"; 69 /** Prefix for when a Private Key is stored directly in the profile @hide */ 70 public static final String PREFIX_INLINE = "INLINE:"; 71 72 private static final String MISSING_PARAM_MSG_TMPL = "Required parameter was not provided: %s"; 73 private static final String EMPTY_CERT = ""; 74 75 /** @hide */ 76 public static final List<String> DEFAULT_ALGORITHMS = 77 Collections.unmodifiableList(Arrays.asList( 78 IpSecAlgorithm.CRYPT_AES_CBC, 79 IpSecAlgorithm.AUTH_HMAC_SHA256, 80 IpSecAlgorithm.AUTH_HMAC_SHA384, 81 IpSecAlgorithm.AUTH_HMAC_SHA512, 82 IpSecAlgorithm.AUTH_CRYPT_AES_GCM)); 83 84 @NonNull private final String mServerAddr; 85 @NonNull private final String mUserIdentity; 86 87 // PSK authentication 88 @Nullable private final byte[] mPresharedKey; 89 90 // Username/Password, RSA authentication 91 @Nullable private final X509Certificate mServerRootCaCert; 92 93 // Username/Password authentication 94 @Nullable private final String mUsername; 95 @Nullable private final String mPassword; 96 97 // RSA Certificate authentication 98 @Nullable private final PrivateKey mRsaPrivateKey; 99 @Nullable private final X509Certificate mUserCert; 100 101 @Nullable private final ProxyInfo mProxyInfo; 102 @NonNull private final List<String> mAllowedAlgorithms; 103 private final boolean mIsBypassable; // Defaults in builder 104 private final boolean mIsMetered; // Defaults in builder 105 private final int mMaxMtu; // Defaults in builder 106 private final boolean mIsRestrictedToTestNetworks; 107 Ikev2VpnProfile( int type, @NonNull String serverAddr, @NonNull String userIdentity, @Nullable byte[] presharedKey, @Nullable X509Certificate serverRootCaCert, @Nullable String username, @Nullable String password, @Nullable PrivateKey rsaPrivateKey, @Nullable X509Certificate userCert, @Nullable ProxyInfo proxyInfo, @NonNull List<String> allowedAlgorithms, boolean isBypassable, boolean isMetered, int maxMtu, boolean restrictToTestNetworks)108 private Ikev2VpnProfile( 109 int type, 110 @NonNull String serverAddr, 111 @NonNull String userIdentity, 112 @Nullable byte[] presharedKey, 113 @Nullable X509Certificate serverRootCaCert, 114 @Nullable String username, 115 @Nullable String password, 116 @Nullable PrivateKey rsaPrivateKey, 117 @Nullable X509Certificate userCert, 118 @Nullable ProxyInfo proxyInfo, 119 @NonNull List<String> allowedAlgorithms, 120 boolean isBypassable, 121 boolean isMetered, 122 int maxMtu, 123 boolean restrictToTestNetworks) { 124 super(type); 125 126 checkNotNull(serverAddr, MISSING_PARAM_MSG_TMPL, "Server address"); 127 checkNotNull(userIdentity, MISSING_PARAM_MSG_TMPL, "User Identity"); 128 checkNotNull(allowedAlgorithms, MISSING_PARAM_MSG_TMPL, "Allowed Algorithms"); 129 130 mServerAddr = serverAddr; 131 mUserIdentity = userIdentity; 132 mPresharedKey = 133 presharedKey == null ? null : Arrays.copyOf(presharedKey, presharedKey.length); 134 mServerRootCaCert = serverRootCaCert; 135 mUsername = username; 136 mPassword = password; 137 mRsaPrivateKey = rsaPrivateKey; 138 mUserCert = userCert; 139 mProxyInfo = new ProxyInfo(proxyInfo); 140 141 // UnmodifiableList doesn't make a defensive copy by default. 142 mAllowedAlgorithms = Collections.unmodifiableList(new ArrayList<>(allowedAlgorithms)); 143 144 mIsBypassable = isBypassable; 145 mIsMetered = isMetered; 146 mMaxMtu = maxMtu; 147 mIsRestrictedToTestNetworks = restrictToTestNetworks; 148 149 validate(); 150 } 151 validate()152 private void validate() { 153 // Server Address not validated except to check an address was provided. This allows for 154 // dual-stack servers and hostname based addresses. 155 checkStringNotEmpty(mServerAddr, MISSING_PARAM_MSG_TMPL, "Server Address"); 156 checkStringNotEmpty(mUserIdentity, MISSING_PARAM_MSG_TMPL, "User Identity"); 157 158 // IPv6 MTU is greater; since profiles may be started by the system on IPv4 and IPv6 159 // networks, the VPN must provide a link fulfilling the stricter of the two conditions 160 // (at least that of the IPv6 MTU). 161 if (mMaxMtu < LinkProperties.MIN_MTU_V6) { 162 throw new IllegalArgumentException( 163 "Max MTU must be at least" + LinkProperties.MIN_MTU_V6); 164 } 165 166 switch (mType) { 167 case TYPE_IKEV2_IPSEC_USER_PASS: 168 checkNotNull(mUsername, MISSING_PARAM_MSG_TMPL, "Username"); 169 checkNotNull(mPassword, MISSING_PARAM_MSG_TMPL, "Password"); 170 171 if (mServerRootCaCert != null) checkCert(mServerRootCaCert); 172 173 break; 174 case TYPE_IKEV2_IPSEC_PSK: 175 checkNotNull(mPresharedKey, MISSING_PARAM_MSG_TMPL, "Preshared Key"); 176 break; 177 case TYPE_IKEV2_IPSEC_RSA: 178 checkNotNull(mUserCert, MISSING_PARAM_MSG_TMPL, "User cert"); 179 checkNotNull(mRsaPrivateKey, MISSING_PARAM_MSG_TMPL, "RSA Private key"); 180 181 checkCert(mUserCert); 182 if (mServerRootCaCert != null) checkCert(mServerRootCaCert); 183 184 break; 185 default: 186 throw new IllegalArgumentException("Invalid auth method set"); 187 } 188 189 validateAllowedAlgorithms(mAllowedAlgorithms); 190 } 191 192 /** 193 * Validates that the allowed algorithms are a valid set for IPsec purposes 194 * 195 * <p>In order for the algorithm list to be a valid set, it must contain at least one algorithm 196 * that provides Authentication, and one that provides Encryption. Authenticated Encryption with 197 * Associated Data (AEAD) algorithms are counted as providing Authentication and Encryption. 198 * 199 * @param allowedAlgorithms The list to be validated 200 */ validateAllowedAlgorithms(@onNull List<String> algorithmNames)201 private static void validateAllowedAlgorithms(@NonNull List<String> algorithmNames) { 202 VpnProfile.validateAllowedAlgorithms(algorithmNames); 203 204 // First, make sure no insecure algorithms were proposed. 205 if (algorithmNames.contains(IpSecAlgorithm.AUTH_HMAC_MD5) 206 || algorithmNames.contains(IpSecAlgorithm.AUTH_HMAC_SHA1)) { 207 throw new IllegalArgumentException("Algorithm not supported for IKEv2 VPN profiles"); 208 } 209 210 // Validate that some valid combination (AEAD or AUTH + CRYPT) is present 211 if (hasAeadAlgorithms(algorithmNames) || hasNormalModeAlgorithms(algorithmNames)) { 212 return; 213 } 214 215 throw new IllegalArgumentException("Algorithm set missing support for Auth, Crypt or both"); 216 } 217 218 /** 219 * Checks if the provided list has AEAD algorithms 220 * 221 * @hide 222 */ hasAeadAlgorithms(@onNull List<String> algorithmNames)223 public static boolean hasAeadAlgorithms(@NonNull List<String> algorithmNames) { 224 return algorithmNames.contains(IpSecAlgorithm.AUTH_CRYPT_AES_GCM); 225 } 226 227 /** 228 * Checks the provided list has acceptable (non-AEAD) authentication and encryption algorithms 229 * 230 * @hide 231 */ hasNormalModeAlgorithms(@onNull List<String> algorithmNames)232 public static boolean hasNormalModeAlgorithms(@NonNull List<String> algorithmNames) { 233 final boolean hasCrypt = algorithmNames.contains(IpSecAlgorithm.CRYPT_AES_CBC); 234 final boolean hasAuth = algorithmNames.contains(IpSecAlgorithm.AUTH_HMAC_SHA256) 235 || algorithmNames.contains(IpSecAlgorithm.AUTH_HMAC_SHA384) 236 || algorithmNames.contains(IpSecAlgorithm.AUTH_HMAC_SHA512); 237 238 return hasCrypt && hasAuth; 239 } 240 241 /** Retrieves the server address string. */ 242 @NonNull getServerAddr()243 public String getServerAddr() { 244 return mServerAddr; 245 } 246 247 /** Retrieves the user identity. */ 248 @NonNull getUserIdentity()249 public String getUserIdentity() { 250 return mUserIdentity; 251 } 252 253 /** 254 * Retrieves the pre-shared key. 255 * 256 * <p>May be null if the profile is not using Pre-shared key authentication. 257 */ 258 @Nullable getPresharedKey()259 public byte[] getPresharedKey() { 260 return mPresharedKey == null ? null : Arrays.copyOf(mPresharedKey, mPresharedKey.length); 261 } 262 263 /** 264 * Retrieves the certificate for the server's root CA. 265 * 266 * <p>May be null if the profile is not using RSA Digital Signature Authentication or 267 * Username/Password authentication 268 */ 269 @Nullable getServerRootCaCert()270 public X509Certificate getServerRootCaCert() { 271 return mServerRootCaCert; 272 } 273 274 /** 275 * Retrieves the username. 276 * 277 * <p>May be null if the profile is not using Username/Password authentication 278 */ 279 @Nullable getUsername()280 public String getUsername() { 281 return mUsername; 282 } 283 284 /** 285 * Retrieves the password. 286 * 287 * <p>May be null if the profile is not using Username/Password authentication 288 */ 289 @Nullable getPassword()290 public String getPassword() { 291 return mPassword; 292 } 293 294 /** 295 * Retrieves the RSA private key. 296 * 297 * <p>May be null if the profile is not using RSA Digital Signature authentication 298 */ 299 @Nullable getRsaPrivateKey()300 public PrivateKey getRsaPrivateKey() { 301 return mRsaPrivateKey; 302 } 303 304 /** Retrieves the user certificate, if any was set. */ 305 @Nullable getUserCert()306 public X509Certificate getUserCert() { 307 return mUserCert; 308 } 309 310 /** Retrieves the proxy information if any was set */ 311 @Nullable getProxyInfo()312 public ProxyInfo getProxyInfo() { 313 return mProxyInfo; 314 } 315 316 /** Returns all the algorithms allowed by this VPN profile. */ 317 @NonNull getAllowedAlgorithms()318 public List<String> getAllowedAlgorithms() { 319 return mAllowedAlgorithms; 320 } 321 322 /** Returns whether or not the VPN profile should be bypassable. */ isBypassable()323 public boolean isBypassable() { 324 return mIsBypassable; 325 } 326 327 /** Returns whether or not the VPN profile should be always considered metered. */ isMetered()328 public boolean isMetered() { 329 return mIsMetered; 330 } 331 332 /** Retrieves the maximum MTU set for this VPN profile. */ getMaxMtu()333 public int getMaxMtu() { 334 return mMaxMtu; 335 } 336 337 /** 338 * Returns whether or not this VPN profile is restricted to test networks. 339 * 340 * @hide 341 */ isRestrictedToTestNetworks()342 public boolean isRestrictedToTestNetworks() { 343 return mIsRestrictedToTestNetworks; 344 } 345 346 @Override hashCode()347 public int hashCode() { 348 return Objects.hash( 349 mType, 350 mServerAddr, 351 mUserIdentity, 352 Arrays.hashCode(mPresharedKey), 353 mServerRootCaCert, 354 mUsername, 355 mPassword, 356 mRsaPrivateKey, 357 mUserCert, 358 mProxyInfo, 359 mAllowedAlgorithms, 360 mIsBypassable, 361 mIsMetered, 362 mMaxMtu, 363 mIsRestrictedToTestNetworks); 364 } 365 366 @Override equals(Object obj)367 public boolean equals(Object obj) { 368 if (!(obj instanceof Ikev2VpnProfile)) { 369 return false; 370 } 371 372 final Ikev2VpnProfile other = (Ikev2VpnProfile) obj; 373 return mType == other.mType 374 && Objects.equals(mServerAddr, other.mServerAddr) 375 && Objects.equals(mUserIdentity, other.mUserIdentity) 376 && Arrays.equals(mPresharedKey, other.mPresharedKey) 377 && Objects.equals(mServerRootCaCert, other.mServerRootCaCert) 378 && Objects.equals(mUsername, other.mUsername) 379 && Objects.equals(mPassword, other.mPassword) 380 && Objects.equals(mRsaPrivateKey, other.mRsaPrivateKey) 381 && Objects.equals(mUserCert, other.mUserCert) 382 && Objects.equals(mProxyInfo, other.mProxyInfo) 383 && Objects.equals(mAllowedAlgorithms, other.mAllowedAlgorithms) 384 && mIsBypassable == other.mIsBypassable 385 && mIsMetered == other.mIsMetered 386 && mMaxMtu == other.mMaxMtu 387 && mIsRestrictedToTestNetworks == other.mIsRestrictedToTestNetworks; 388 } 389 390 /** 391 * Builds a VpnProfile instance for internal use, based on the stored IKEv2/IPsec parameters. 392 * 393 * <p>Redundant authentication information (from previous calls to other setAuth* methods) will 394 * be discarded. 395 * 396 * @hide 397 */ 398 @NonNull toVpnProfile()399 public VpnProfile toVpnProfile() throws IOException, GeneralSecurityException { 400 final VpnProfile profile = new VpnProfile("" /* Key; value unused by IKEv2VpnProfile(s) */, 401 mIsRestrictedToTestNetworks); 402 profile.type = mType; 403 profile.server = mServerAddr; 404 profile.ipsecIdentifier = mUserIdentity; 405 profile.proxy = mProxyInfo; 406 profile.setAllowedAlgorithms(mAllowedAlgorithms); 407 profile.isBypassable = mIsBypassable; 408 profile.isMetered = mIsMetered; 409 profile.maxMtu = mMaxMtu; 410 profile.areAuthParamsInline = true; 411 profile.saveLogin = true; 412 413 switch (mType) { 414 case TYPE_IKEV2_IPSEC_USER_PASS: 415 profile.username = mUsername; 416 profile.password = mPassword; 417 profile.ipsecCaCert = 418 mServerRootCaCert == null ? "" : certificateToPemString(mServerRootCaCert); 419 break; 420 case TYPE_IKEV2_IPSEC_PSK: 421 profile.ipsecSecret = encodeForIpsecSecret(mPresharedKey); 422 break; 423 case TYPE_IKEV2_IPSEC_RSA: 424 profile.ipsecUserCert = certificateToPemString(mUserCert); 425 profile.ipsecSecret = 426 PREFIX_INLINE + encodeForIpsecSecret(mRsaPrivateKey.getEncoded()); 427 profile.ipsecCaCert = 428 mServerRootCaCert == null ? "" : certificateToPemString(mServerRootCaCert); 429 break; 430 default: 431 throw new IllegalArgumentException("Invalid auth method set"); 432 } 433 434 return profile; 435 } 436 437 /** 438 * Constructs a Ikev2VpnProfile from an internal-use VpnProfile instance. 439 * 440 * <p>Redundant authentication information (not related to profile type) will be discarded. 441 * 442 * @hide 443 */ 444 @NonNull fromVpnProfile(@onNull VpnProfile profile)445 public static Ikev2VpnProfile fromVpnProfile(@NonNull VpnProfile profile) 446 throws IOException, GeneralSecurityException { 447 return fromVpnProfile(profile, null); 448 } 449 450 /** 451 * Builds the Ikev2VpnProfile from the given profile. 452 * 453 * @param profile the source VpnProfile to build from 454 * @param keyStore the Android Keystore instance to use to retrieve the private key, or null if 455 * the private key is PEM-encoded into the profile. 456 * @return The IKEv2/IPsec VPN profile 457 * @hide 458 */ 459 @NonNull fromVpnProfile( @onNull VpnProfile profile, @Nullable KeyStore keyStore)460 public static Ikev2VpnProfile fromVpnProfile( 461 @NonNull VpnProfile profile, @Nullable KeyStore keyStore) 462 throws IOException, GeneralSecurityException { 463 final Builder builder = new Builder(profile.server, profile.ipsecIdentifier); 464 builder.setProxy(profile.proxy); 465 builder.setAllowedAlgorithms(profile.getAllowedAlgorithms()); 466 builder.setBypassable(profile.isBypassable); 467 builder.setMetered(profile.isMetered); 468 builder.setMaxMtu(profile.maxMtu); 469 if (profile.isRestrictedToTestNetworks) { 470 builder.restrictToTestNetworks(); 471 } 472 473 switch (profile.type) { 474 case TYPE_IKEV2_IPSEC_USER_PASS: 475 builder.setAuthUsernamePassword( 476 profile.username, 477 profile.password, 478 certificateFromPemString(profile.ipsecCaCert)); 479 break; 480 case TYPE_IKEV2_IPSEC_PSK: 481 builder.setAuthPsk(decodeFromIpsecSecret(profile.ipsecSecret)); 482 break; 483 case TYPE_IKEV2_IPSEC_RSA: 484 final PrivateKey key; 485 if (profile.ipsecSecret.startsWith(PREFIX_KEYSTORE_ALIAS)) { 486 Objects.requireNonNull(keyStore, "Missing Keystore for aliased PrivateKey"); 487 488 final String alias = 489 profile.ipsecSecret.substring(PREFIX_KEYSTORE_ALIAS.length()); 490 key = AndroidKeyStoreProvider.loadAndroidKeyStorePrivateKeyFromKeystore( 491 keyStore, alias, Process.myUid()); 492 } else if (profile.ipsecSecret.startsWith(PREFIX_INLINE)) { 493 key = getPrivateKey(profile.ipsecSecret.substring(PREFIX_INLINE.length())); 494 } else { 495 throw new IllegalArgumentException("Invalid RSA private key prefix"); 496 } 497 498 final X509Certificate userCert = certificateFromPemString(profile.ipsecUserCert); 499 final X509Certificate serverRootCa = certificateFromPemString(profile.ipsecCaCert); 500 builder.setAuthDigitalSignature(userCert, key, serverRootCa); 501 break; 502 default: 503 throw new IllegalArgumentException("Invalid auth method set"); 504 } 505 506 return builder.build(); 507 } 508 509 /** 510 * Validates that the VpnProfile is acceptable for the purposes of an Ikev2VpnProfile. 511 * 512 * @hide 513 */ isValidVpnProfile(@onNull VpnProfile profile)514 public static boolean isValidVpnProfile(@NonNull VpnProfile profile) { 515 if (profile.server.isEmpty() || profile.ipsecIdentifier.isEmpty()) { 516 return false; 517 } 518 519 switch (profile.type) { 520 case TYPE_IKEV2_IPSEC_USER_PASS: 521 if (profile.username.isEmpty() || profile.password.isEmpty()) { 522 return false; 523 } 524 break; 525 case TYPE_IKEV2_IPSEC_PSK: 526 if (profile.ipsecSecret.isEmpty()) { 527 return false; 528 } 529 break; 530 case TYPE_IKEV2_IPSEC_RSA: 531 if (profile.ipsecSecret.isEmpty() || profile.ipsecUserCert.isEmpty()) { 532 return false; 533 } 534 break; 535 default: 536 return false; 537 } 538 539 return true; 540 } 541 542 /** 543 * Converts a X509 Certificate to a PEM-formatted string. 544 * 545 * <p>Must be public due to runtime-package restrictions. 546 * 547 * @hide 548 */ 549 @NonNull 550 @VisibleForTesting(visibility = Visibility.PRIVATE) certificateToPemString(@ullable X509Certificate cert)551 public static String certificateToPemString(@Nullable X509Certificate cert) 552 throws IOException, CertificateEncodingException { 553 if (cert == null) { 554 return EMPTY_CERT; 555 } 556 557 // Credentials.convertToPem outputs ASCII bytes. 558 return new String(Credentials.convertToPem(cert), StandardCharsets.US_ASCII); 559 } 560 561 /** 562 * Decodes the provided Certificate(s). 563 * 564 * <p>Will use the first one if the certStr encodes more than one certificate. 565 */ 566 @Nullable certificateFromPemString(@ullable String certStr)567 private static X509Certificate certificateFromPemString(@Nullable String certStr) 568 throws CertificateException { 569 if (certStr == null || EMPTY_CERT.equals(certStr)) { 570 return null; 571 } 572 573 try { 574 final List<X509Certificate> certs = 575 Credentials.convertFromPem(certStr.getBytes(StandardCharsets.US_ASCII)); 576 return certs.isEmpty() ? null : certs.get(0); 577 } catch (IOException e) { 578 throw new CertificateException(e); 579 } 580 } 581 582 /** @hide */ 583 @NonNull encodeForIpsecSecret(@onNull byte[] secret)584 public static String encodeForIpsecSecret(@NonNull byte[] secret) { 585 checkNotNull(secret, MISSING_PARAM_MSG_TMPL, "secret"); 586 587 return Base64.getEncoder().encodeToString(secret); 588 } 589 590 @NonNull decodeFromIpsecSecret(@onNull String encoded)591 private static byte[] decodeFromIpsecSecret(@NonNull String encoded) { 592 checkNotNull(encoded, MISSING_PARAM_MSG_TMPL, "encoded"); 593 594 return Base64.getDecoder().decode(encoded); 595 } 596 597 @NonNull getPrivateKey(@onNull String keyStr)598 private static PrivateKey getPrivateKey(@NonNull String keyStr) 599 throws InvalidKeySpecException, NoSuchAlgorithmException { 600 final PKCS8EncodedKeySpec privateKeySpec = 601 new PKCS8EncodedKeySpec(decodeFromIpsecSecret(keyStr)); 602 final KeyFactory keyFactory = KeyFactory.getInstance("RSA"); 603 return keyFactory.generatePrivate(privateKeySpec); 604 } 605 checkCert(@onNull X509Certificate cert)606 private static void checkCert(@NonNull X509Certificate cert) { 607 try { 608 certificateToPemString(cert); 609 } catch (GeneralSecurityException | IOException e) { 610 throw new IllegalArgumentException("Certificate could not be encoded"); 611 } 612 } 613 checkNotNull( final T reference, final String messageTemplate, final Object... messageArgs)614 private static @NonNull <T> T checkNotNull( 615 final T reference, final String messageTemplate, final Object... messageArgs) { 616 return Objects.requireNonNull(reference, String.format(messageTemplate, messageArgs)); 617 } 618 619 /** A incremental builder for IKEv2 VPN profiles */ 620 public static final class Builder { 621 private int mType = -1; 622 @NonNull private final String mServerAddr; 623 @NonNull private final String mUserIdentity; 624 625 // PSK authentication 626 @Nullable private byte[] mPresharedKey; 627 628 // Username/Password, RSA authentication 629 @Nullable private X509Certificate mServerRootCaCert; 630 631 // Username/Password authentication 632 @Nullable private String mUsername; 633 @Nullable private String mPassword; 634 635 // RSA Certificate authentication 636 @Nullable private PrivateKey mRsaPrivateKey; 637 @Nullable private X509Certificate mUserCert; 638 639 @Nullable private ProxyInfo mProxyInfo; 640 @NonNull private List<String> mAllowedAlgorithms = DEFAULT_ALGORITHMS; 641 private boolean mIsBypassable = false; 642 private boolean mIsMetered = true; 643 private int mMaxMtu = PlatformVpnProfile.MAX_MTU_DEFAULT; 644 private boolean mIsRestrictedToTestNetworks = false; 645 646 /** 647 * Creates a new builder with the basic parameters of an IKEv2/IPsec VPN. 648 * 649 * @param serverAddr the server that the VPN should connect to 650 * @param identity the identity string to be used for IKEv2 authentication 651 */ 652 @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS) Builder(@onNull String serverAddr, @NonNull String identity)653 public Builder(@NonNull String serverAddr, @NonNull String identity) { 654 checkNotNull(serverAddr, MISSING_PARAM_MSG_TMPL, "serverAddr"); 655 checkNotNull(identity, MISSING_PARAM_MSG_TMPL, "identity"); 656 657 mServerAddr = serverAddr; 658 mUserIdentity = identity; 659 } 660 resetAuthParams()661 private void resetAuthParams() { 662 mPresharedKey = null; 663 mServerRootCaCert = null; 664 mUsername = null; 665 mPassword = null; 666 mRsaPrivateKey = null; 667 mUserCert = null; 668 } 669 670 /** 671 * Set the IKEv2 authentication to use the provided username/password. 672 * 673 * <p>Setting this will configure IKEv2 authentication using EAP-MSCHAPv2. Only one 674 * authentication method may be set. This method will overwrite any previously set 675 * authentication method. 676 * 677 * @param user the username to be used for EAP-MSCHAPv2 authentication 678 * @param pass the password to be used for EAP-MSCHAPv2 authentication 679 * @param serverRootCa the root certificate to be used for verifying the identity of the 680 * server 681 * @return this {@link Builder} object to facilitate chaining of method calls 682 * @throws IllegalArgumentException if any of the certificates were invalid or of an 683 * unrecognized format 684 */ 685 @NonNull 686 @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS) setAuthUsernamePassword( @onNull String user, @NonNull String pass, @Nullable X509Certificate serverRootCa)687 public Builder setAuthUsernamePassword( 688 @NonNull String user, 689 @NonNull String pass, 690 @Nullable X509Certificate serverRootCa) { 691 checkNotNull(user, MISSING_PARAM_MSG_TMPL, "user"); 692 checkNotNull(pass, MISSING_PARAM_MSG_TMPL, "pass"); 693 694 // Test to make sure all auth params can be encoded safely. 695 if (serverRootCa != null) checkCert(serverRootCa); 696 697 resetAuthParams(); 698 mUsername = user; 699 mPassword = pass; 700 mServerRootCaCert = serverRootCa; 701 mType = VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS; 702 return this; 703 } 704 705 /** 706 * Set the IKEv2 authentication to use Digital Signature Authentication with the given key. 707 * 708 * <p>Setting this will configure IKEv2 authentication using a Digital Signature scheme. 709 * Only one authentication method may be set. This method will overwrite any previously set 710 * authentication method. 711 * 712 * @param userCert the username to be used for RSA Digital signiture authentication 713 * @param key the PrivateKey instance associated with the user ceritificate, used for 714 * constructing the signature 715 * @param serverRootCa the root certificate to be used for verifying the identity of the 716 * server 717 * @return this {@link Builder} object to facilitate chaining of method calls 718 * @throws IllegalArgumentException if any of the certificates were invalid or of an 719 * unrecognized format 720 */ 721 @NonNull 722 @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS) setAuthDigitalSignature( @onNull X509Certificate userCert, @NonNull PrivateKey key, @Nullable X509Certificate serverRootCa)723 public Builder setAuthDigitalSignature( 724 @NonNull X509Certificate userCert, 725 @NonNull PrivateKey key, 726 @Nullable X509Certificate serverRootCa) { 727 checkNotNull(userCert, MISSING_PARAM_MSG_TMPL, "userCert"); 728 checkNotNull(key, MISSING_PARAM_MSG_TMPL, "key"); 729 730 // Test to make sure all auth params can be encoded safely. 731 checkCert(userCert); 732 if (serverRootCa != null) checkCert(serverRootCa); 733 734 resetAuthParams(); 735 mUserCert = userCert; 736 mRsaPrivateKey = key; 737 mServerRootCaCert = serverRootCa; 738 mType = VpnProfile.TYPE_IKEV2_IPSEC_RSA; 739 return this; 740 } 741 742 /** 743 * Set the IKEv2 authentication to use Preshared keys. 744 * 745 * <p>Setting this will configure IKEv2 authentication using a Preshared Key. Only one 746 * authentication method may be set. This method will overwrite any previously set 747 * authentication method. 748 * 749 * @param psk the key to be used for Pre-Shared Key authentication 750 * @return this {@link Builder} object to facilitate chaining of method calls 751 */ 752 @NonNull 753 @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS) setAuthPsk(@onNull byte[] psk)754 public Builder setAuthPsk(@NonNull byte[] psk) { 755 checkNotNull(psk, MISSING_PARAM_MSG_TMPL, "psk"); 756 757 resetAuthParams(); 758 mPresharedKey = psk; 759 mType = VpnProfile.TYPE_IKEV2_IPSEC_PSK; 760 return this; 761 } 762 763 /** 764 * Sets whether apps can bypass this VPN connection. 765 * 766 * <p>By default, all traffic from apps are forwarded through the VPN interface and it is 767 * not possible for unprivileged apps to side-step the VPN. If a VPN is set to bypassable, 768 * apps may use methods such as {@link Network#getSocketFactory} or {@link 769 * Network#openConnection} to instead send/receive directly over the underlying network or 770 * any other network they have permissions for. 771 * 772 * @param isBypassable Whether or not the VPN should be considered bypassable. Defaults to 773 * {@code false}. 774 * @return this {@link Builder} object to facilitate chaining of method calls 775 */ 776 @NonNull 777 @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS) setBypassable(boolean isBypassable)778 public Builder setBypassable(boolean isBypassable) { 779 mIsBypassable = isBypassable; 780 return this; 781 } 782 783 /** 784 * Sets a proxy for the VPN network. 785 * 786 * <p>Note that this proxy is only a recommendation and it may be ignored by apps. 787 * 788 * @param proxy the ProxyInfo to be set for the VPN network 789 * @return this {@link Builder} object to facilitate chaining of method calls 790 */ 791 @NonNull 792 @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS) setProxy(@ullable ProxyInfo proxy)793 public Builder setProxy(@Nullable ProxyInfo proxy) { 794 mProxyInfo = proxy; 795 return this; 796 } 797 798 /** 799 * Set the upper bound of the maximum transmission unit (MTU) of the VPN interface. 800 * 801 * <p>If it is not set, a safe value will be used. Additionally, the actual link MTU will be 802 * dynamically calculated/updated based on the underlying link's mtu. 803 * 804 * @param mtu the MTU (in bytes) of the VPN interface 805 * @return this {@link Builder} object to facilitate chaining of method calls 806 * @throws IllegalArgumentException if the value is not at least the minimum IPv6 MTU (1280) 807 */ 808 @NonNull 809 @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS) setMaxMtu(int mtu)810 public Builder setMaxMtu(int mtu) { 811 // IPv6 MTU is greater; since profiles may be started by the system on IPv4 and IPv6 812 // networks, the VPN must provide a link fulfilling the stricter of the two conditions 813 // (at least that of the IPv6 MTU). 814 if (mtu < LinkProperties.MIN_MTU_V6) { 815 throw new IllegalArgumentException( 816 "Max MTU must be at least " + LinkProperties.MIN_MTU_V6); 817 } 818 mMaxMtu = mtu; 819 return this; 820 } 821 822 /** 823 * Marks the VPN network as metered. 824 * 825 * <p>A VPN network is classified as metered when the user is sensitive to heavy data usage 826 * due to monetary costs and/or data limitations. In such cases, you should set this to 827 * {@code true} so that apps on the system can avoid doing large data transfers. Otherwise, 828 * set this to {@code false}. Doing so would cause VPN network to inherit its meteredness 829 * from the underlying network. 830 * 831 * @param isMetered {@code true} if the VPN network should be treated as metered regardless 832 * of underlying network meteredness. Defaults to {@code true}. 833 * @return this {@link Builder} object to facilitate chaining of method calls 834 * @see NetworkCapabilities#NET_CAPABILITY_NOT_METERED 835 */ 836 @NonNull 837 @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS) setMetered(boolean isMetered)838 public Builder setMetered(boolean isMetered) { 839 mIsMetered = isMetered; 840 return this; 841 } 842 843 /** 844 * Sets the allowable set of IPsec algorithms 845 * 846 * <p>If set, this will constrain the set of algorithms that the IPsec tunnel will use for 847 * integrity verification and encryption to the provided list. 848 * 849 * <p>The set of allowed IPsec algorithms is defined in {@link IpSecAlgorithm}. Adding of 850 * algorithms that are considered insecure (such as AUTH_HMAC_MD5 and AUTH_HMAC_SHA1) is not 851 * permitted, and will result in an IllegalArgumentException being thrown. 852 * 853 * <p>The provided algorithm list must contain at least one algorithm that provides 854 * Authentication, and one that provides Encryption. Authenticated Encryption with 855 * Associated Data (AEAD) algorithms provide both Authentication and Encryption. 856 * 857 * <p>By default, this profile will use any algorithm defined in {@link IpSecAlgorithm}, 858 * with the exception of those considered insecure (as described above). 859 * 860 * @param algorithmNames the list of supported IPsec algorithms 861 * @return this {@link Builder} object to facilitate chaining of method calls 862 * @see IpSecAlgorithm 863 */ 864 @NonNull 865 @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS) setAllowedAlgorithms(@onNull List<String> algorithmNames)866 public Builder setAllowedAlgorithms(@NonNull List<String> algorithmNames) { 867 checkNotNull(algorithmNames, MISSING_PARAM_MSG_TMPL, "algorithmNames"); 868 validateAllowedAlgorithms(algorithmNames); 869 870 mAllowedAlgorithms = algorithmNames; 871 return this; 872 } 873 874 /** 875 * Restricts this profile to use test networks (only). 876 * 877 * <p>This method is for testing only, and must not be used by apps. Calling 878 * provisionVpnProfile() with a profile where test-network usage is enabled will require the 879 * MANAGE_TEST_NETWORKS permission. 880 * 881 * @hide 882 */ 883 @NonNull 884 @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS) restrictToTestNetworks()885 public Builder restrictToTestNetworks() { 886 mIsRestrictedToTestNetworks = true; 887 return this; 888 } 889 890 /** 891 * Validates, builds and provisions the VpnProfile. 892 * 893 * @throws IllegalArgumentException if any of the required keys or values were invalid 894 */ 895 @NonNull 896 @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS) build()897 public Ikev2VpnProfile build() { 898 return new Ikev2VpnProfile( 899 mType, 900 mServerAddr, 901 mUserIdentity, 902 mPresharedKey, 903 mServerRootCaCert, 904 mUsername, 905 mPassword, 906 mRsaPrivateKey, 907 mUserCert, 908 mProxyInfo, 909 mAllowedAlgorithms, 910 mIsBypassable, 911 mIsMetered, 912 mMaxMtu, 913 mIsRestrictedToTestNetworks); 914 } 915 } 916 } 917