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