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