• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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