1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.internal.net;
18 
19 import android.annotation.NonNull;
20 import android.compat.annotation.UnsupportedAppUsage;
21 import android.net.Ikev2VpnProfile;
22 import android.net.PlatformVpnProfile;
23 import android.net.ProxyInfo;
24 import android.net.Uri;
25 import android.net.ipsec.ike.IkeTunnelConnectionParams;
26 import android.net.vcn.persistablebundleutils.TunnelConnectionParamsUtils;
27 import android.os.Build;
28 import android.os.Parcel;
29 import android.os.Parcelable;
30 import android.os.PersistableBundle;
31 import android.text.TextUtils;
32 import android.util.Log;
33 
34 import com.android.internal.annotations.VisibleForTesting;
35 import com.android.internal.util.HexDump;
36 import com.android.net.module.util.ProxyUtils;
37 
38 import java.io.UnsupportedEncodingException;
39 import java.net.InetAddress;
40 import java.net.URLDecoder;
41 import java.net.URLEncoder;
42 import java.nio.charset.StandardCharsets;
43 import java.util.ArrayList;
44 import java.util.Arrays;
45 import java.util.Collections;
46 import java.util.List;
47 import java.util.Objects;
48 
49 /**
50  * Profile storage class for a platform VPN.
51  *
52  * <p>This class supports both the Legacy VPN, as well as application-configurable platform VPNs
53  * (such as IKEv2/IPsec).
54  *
55  * <p>This class is serialized and deserialized via the {@link #encode()} and {@link #decode()}
56  * functions for persistent storage in the Android Keystore. The encoding is entirely custom, but
57  * must be kept for backward compatibility for devices upgrading between Android versions.
58  *
59  * @hide
60  */
61 public final class VpnProfile implements Cloneable, Parcelable {
62     private static final String TAG = "VpnProfile";
63 
64     @VisibleForTesting static final String VALUE_DELIMITER = "\0";
65     @VisibleForTesting static final String LIST_DELIMITER = ",";
66 
67     // Match these constants with R.array.vpn_types.
68     public static final int TYPE_PPTP = 0;
69     public static final int TYPE_L2TP_IPSEC_PSK = 1;
70     public static final int TYPE_L2TP_IPSEC_RSA = 2;
71     public static final int TYPE_IPSEC_XAUTH_PSK = 3;
72     public static final int TYPE_IPSEC_XAUTH_RSA = 4;
73     public static final int TYPE_IPSEC_HYBRID_RSA = 5;
74     public static final int TYPE_IKEV2_IPSEC_USER_PASS = 6;
75     public static final int TYPE_IKEV2_IPSEC_PSK = 7;
76     public static final int TYPE_IKEV2_IPSEC_RSA = 8;
77     public static final int TYPE_IKEV2_FROM_IKE_TUN_CONN_PARAMS = 9;
78     public static final int TYPE_MAX = 9;
79 
80     // Match these constants with R.array.vpn_proxy_settings.
81     public static final int PROXY_NONE = 0;
82     public static final int PROXY_MANUAL = 1;
83 
84     private static final String ENCODED_NULL_PROXY_INFO = "\0\0\0\0";
85 
86     /** Default URL encoding. */
87     private static final String DEFAULT_ENCODING = StandardCharsets.UTF_8.name();
88 
89     // Entity fields.
90     @UnsupportedAppUsage
91     public final String key;                                   // -1
92 
93     @UnsupportedAppUsage
94     public String name = "";                                   // 0
95 
96     @UnsupportedAppUsage
97     public int type = TYPE_PPTP;                               // 1
98 
99     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
100     public String server = "";                                 // 2
101 
102     @UnsupportedAppUsage
103     public String username = "";                               // 3
104     public String password = "";                               // 4
105     public String dnsServers = "";                             // 5
106     public String searchDomains = "";                          // 6
107     public String routes = "";                                 // 7
108     public boolean mppe = true;                                // 8
109     public String l2tpSecret = "";                             // 9
110     public String ipsecIdentifier = "";                        // 10
111 
112     /**
113      * The RSA private key or pre-shared key used for authentication.
114      *
115      * <p>If areAuthParamsInline is {@code true}, this String will be either:
116      *
117      * <ul>
118      *   <li>If this is an IKEv2 RSA profile: a PKCS#8 encoded {@link java.security.PrivateKey}
119      *   <li>If this is an IKEv2 PSK profile: a string value representing the PSK.
120      * </ul>
121      */
122     public String ipsecSecret = "";                            // 11
123 
124     /**
125      * The RSA certificate to be used for digital signature authentication.
126      *
127      * <p>If areAuthParamsInline is {@code true}, this String will be a pem-encoded {@link
128      * java.security.X509Certificate}
129      */
130     public String ipsecUserCert = "";                          // 12
131 
132     /**
133      * The RSA certificate that should be used to verify the server's end/target certificate.
134      *
135      * <p>If areAuthParamsInline is {@code true}, this String will be a pem-encoded {@link
136      * java.security.X509Certificate}
137      */
138     public String ipsecCaCert = "";                            // 13
139     public String ipsecServerCert = "";                        // 14
140     public ProxyInfo proxy = null;                             // 15~18
141 
142     /**
143      * The list of allowable algorithms.
144      */
145     private List<String> mAllowedAlgorithms = new ArrayList<>(); // 19
146     public boolean isBypassable = false;                         // 20
147     public boolean isMetered = false;                            // 21
148     public int maxMtu = PlatformVpnProfile.MAX_MTU_DEFAULT;      // 22
149     public boolean areAuthParamsInline = false;                  // 23
150     public final boolean isRestrictedToTestNetworks;             // 24
151 
152     public final boolean excludeLocalRoutes;                     // 25
153     public final boolean requiresInternetValidation;             // 26
154     public final IkeTunnelConnectionParams ikeTunConnParams;     // 27
155     public final boolean automaticNattKeepaliveTimerEnabled;     // 28
156     public final boolean automaticIpVersionSelectionEnabled;     // 29
157 
158     // Helper fields.
159     @UnsupportedAppUsage
160     public transient boolean saveLogin = false;
161 
VpnProfile(String key)162     public VpnProfile(String key) {
163         this(key, false, false, false, null);
164     }
165 
VpnProfile(String key, boolean isRestrictedToTestNetworks)166     public VpnProfile(String key, boolean isRestrictedToTestNetworks) {
167         this(key, isRestrictedToTestNetworks, false, false, null);
168     }
169 
VpnProfile(String key, boolean isRestrictedToTestNetworks, boolean excludeLocalRoutes, boolean requiresInternetValidation, IkeTunnelConnectionParams ikeTunConnParams)170     public VpnProfile(String key, boolean isRestrictedToTestNetworks, boolean excludeLocalRoutes,
171             boolean requiresInternetValidation, IkeTunnelConnectionParams ikeTunConnParams) {
172         this(key, isRestrictedToTestNetworks, excludeLocalRoutes, requiresInternetValidation,
173                 ikeTunConnParams, false, false);
174     }
175 
VpnProfile(String key, boolean isRestrictedToTestNetworks, boolean excludeLocalRoutes, boolean requiresInternetValidation, IkeTunnelConnectionParams ikeTunConnParams, boolean automaticNattKeepaliveTimerEnabled, boolean automaticIpVersionSelectionEnabled)176     public VpnProfile(String key, boolean isRestrictedToTestNetworks, boolean excludeLocalRoutes,
177             boolean requiresInternetValidation, IkeTunnelConnectionParams ikeTunConnParams,
178             boolean automaticNattKeepaliveTimerEnabled,
179             boolean automaticIpVersionSelectionEnabled) {
180         this.key = key;
181         this.isRestrictedToTestNetworks = isRestrictedToTestNetworks;
182         this.excludeLocalRoutes = excludeLocalRoutes;
183         this.requiresInternetValidation = requiresInternetValidation;
184         this.ikeTunConnParams = ikeTunConnParams;
185         this.automaticNattKeepaliveTimerEnabled = automaticNattKeepaliveTimerEnabled;
186         this.automaticIpVersionSelectionEnabled = automaticIpVersionSelectionEnabled;
187     }
188 
189     @UnsupportedAppUsage
VpnProfile(Parcel in)190     public VpnProfile(Parcel in) {
191         key = in.readString();
192         name = in.readString();
193         type = in.readInt();
194         server = in.readString();
195         username = in.readString();
196         password = in.readString();
197         dnsServers = in.readString();
198         searchDomains = in.readString();
199         routes = in.readString();
200         mppe = in.readInt() != 0;
201         l2tpSecret = in.readString();
202         ipsecIdentifier = in.readString();
203         ipsecSecret = in.readString();
204         ipsecUserCert = in.readString();
205         ipsecCaCert = in.readString();
206         ipsecServerCert = in.readString();
207         saveLogin = in.readInt() != 0;
208         proxy = in.readParcelable(null, android.net.ProxyInfo.class);
209         mAllowedAlgorithms = new ArrayList<>();
210         in.readList(mAllowedAlgorithms, null, java.lang.String.class);
211         isBypassable = in.readBoolean();
212         isMetered = in.readBoolean();
213         maxMtu = in.readInt();
214         areAuthParamsInline = in.readBoolean();
215         isRestrictedToTestNetworks = in.readBoolean();
216         excludeLocalRoutes = in.readBoolean();
217         requiresInternetValidation = in.readBoolean();
218         final PersistableBundle bundle =
219                 in.readParcelable(PersistableBundle.class.getClassLoader(), android.os.PersistableBundle.class);
220         ikeTunConnParams = (bundle == null) ? null
221                 : TunnelConnectionParamsUtils.fromPersistableBundle(bundle);
222         automaticNattKeepaliveTimerEnabled = in.readBoolean();
223         automaticIpVersionSelectionEnabled = in.readBoolean();
224     }
225 
226     /**
227      * Retrieves the list of allowed algorithms.
228      *
229      * <p>The contained elements are as listed in {@link IpSecAlgorithm}
230      */
getAllowedAlgorithms()231     public List<String> getAllowedAlgorithms() {
232         return Collections.unmodifiableList(mAllowedAlgorithms);
233     }
234 
235     /**
236      * Validates and sets the list of algorithms that can be used for the IPsec transforms.
237      *
238      * @param allowedAlgorithms the list of allowable algorithms, as listed in {@link
239      *     IpSecAlgorithm}.
240      */
setAllowedAlgorithms(List<String> allowedAlgorithms)241     public void setAllowedAlgorithms(List<String> allowedAlgorithms) {
242         mAllowedAlgorithms = allowedAlgorithms;
243     }
244 
245     @Override
writeToParcel(Parcel out, int flags)246     public void writeToParcel(Parcel out, int flags) {
247         out.writeString(key);
248         out.writeString(name);
249         out.writeInt(type);
250         out.writeString(server);
251         out.writeString(username);
252         out.writeString(password);
253         out.writeString(dnsServers);
254         out.writeString(searchDomains);
255         out.writeString(routes);
256         out.writeInt(mppe ? 1 : 0);
257         out.writeString(l2tpSecret);
258         out.writeString(ipsecIdentifier);
259         out.writeString(ipsecSecret);
260         out.writeString(ipsecUserCert);
261         out.writeString(ipsecCaCert);
262         out.writeString(ipsecServerCert);
263         out.writeInt(saveLogin ? 1 : 0);
264         out.writeParcelable(proxy, flags);
265         out.writeList(mAllowedAlgorithms);
266         out.writeBoolean(isBypassable);
267         out.writeBoolean(isMetered);
268         out.writeInt(maxMtu);
269         out.writeBoolean(areAuthParamsInline);
270         out.writeBoolean(isRestrictedToTestNetworks);
271         out.writeBoolean(excludeLocalRoutes);
272         out.writeBoolean(requiresInternetValidation);
273         out.writeParcelable(ikeTunConnParams == null ? null
274                 : TunnelConnectionParamsUtils.toPersistableBundle(ikeTunConnParams), flags);
275         out.writeBoolean(automaticNattKeepaliveTimerEnabled);
276         out.writeBoolean(automaticIpVersionSelectionEnabled);
277     }
278 
279     /**
280      * Decodes a VpnProfile instance from the encoded byte array.
281      *
282      * <p>See {@link #encode()}
283      */
284     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
decode(String key, byte[] value)285     public static VpnProfile decode(String key, byte[] value) {
286         try {
287             if (key == null) {
288                 return null;
289             }
290 
291             String[] values = new String(value, StandardCharsets.UTF_8).split(VALUE_DELIMITER, -1);
292 
293             // Acceptable numbers of values are:
294             // 14-19: Standard profile, with option for serverCert, proxy
295             // 24: Standard profile with serverCert, proxy and platform-VPN parameters
296             // 25: Standard profile with platform-VPN parameters and isRestrictedToTestNetworks
297             // 26:                                            ...and excludeLocalRoutes
298             // 27:                                            ...and requiresInternetValidation
299             //     (26,27 can only be found on dogfood devices)
300             // 28:                                            ...and ikeTunConnParams
301             // 29-30:                                         ...and automatic NATT/IP version
302             if ((values.length < 14 || (values.length > 19 && values.length < 24)
303                     || (values.length > 28 && values.length < 30) || values.length > 30)) {
304                 return null;
305             }
306 
307             final boolean isRestrictedToTestNetworks;
308             if (values.length >= 25) {
309                 isRestrictedToTestNetworks = Boolean.parseBoolean(values[24]);
310             } else {
311                 isRestrictedToTestNetworks = false;
312             }
313 
314             final boolean excludeLocalRoutes;
315             if (values.length >= 26) {
316                 excludeLocalRoutes = Boolean.parseBoolean(values[25]);
317             } else {
318                 excludeLocalRoutes = false;
319             }
320 
321             final boolean requiresInternetValidation;
322             if (values.length >= 27) {
323                 requiresInternetValidation = Boolean.parseBoolean(values[26]);
324             } else {
325                 requiresInternetValidation = false;
326             }
327 
328             final IkeTunnelConnectionParams tempIkeTunConnParams;
329             // Assign null directly if the ikeTunConParams field is empty.
330             if (values.length >= 28 && values[27].length() != 0) {
331                 final Parcel parcel = Parcel.obtain();
332                 final byte[] bytes = HexDump.hexStringToByteArray(values[27]);
333                 parcel.unmarshall(bytes, 0, bytes.length);
334                 parcel.setDataPosition(0);
335                 final PersistableBundle bundle = (PersistableBundle) parcel.readValue(
336                         PersistableBundle.class.getClassLoader());
337                 tempIkeTunConnParams = TunnelConnectionParamsUtils.fromPersistableBundle(bundle);
338             } else {
339                 tempIkeTunConnParams = null;
340             }
341 
342             final boolean automaticNattKeepaliveTimerEnabled;
343             final boolean automaticIpVersionSelectionEnabled;
344             if (values.length >= 30) {
345                 automaticNattKeepaliveTimerEnabled = Boolean.parseBoolean(values[28]);
346                 automaticIpVersionSelectionEnabled = Boolean.parseBoolean(values[29]);
347             } else {
348                 automaticNattKeepaliveTimerEnabled = false;
349                 automaticIpVersionSelectionEnabled = false;
350             }
351 
352             VpnProfile profile = new VpnProfile(key, isRestrictedToTestNetworks,
353                     excludeLocalRoutes, requiresInternetValidation, tempIkeTunConnParams,
354                     automaticNattKeepaliveTimerEnabled, automaticIpVersionSelectionEnabled);
355             profile.name = values[0];
356             profile.type = Integer.parseInt(values[1]);
357             if (profile.type < 0 || profile.type > TYPE_MAX) {
358                 return null;
359             }
360             profile.server = values[2];
361             profile.username = values[3];
362             profile.password = values[4];
363             profile.dnsServers = values[5];
364             profile.searchDomains = values[6];
365             profile.routes = values[7];
366             profile.mppe = Boolean.parseBoolean(values[8]);
367             profile.l2tpSecret = values[9];
368             profile.ipsecIdentifier = values[10];
369             profile.ipsecSecret = values[11];
370             profile.ipsecUserCert = values[12];
371             profile.ipsecCaCert = values[13];
372             profile.ipsecServerCert = (values.length > 14) ? values[14] : "";
373             if (values.length > 15) {
374                 String host = (values.length > 15) ? values[15] : "";
375                 String port = (values.length > 16) ? values[16] : "";
376                 String exclList = (values.length > 17) ? values[17] : "";
377                 String pacFileUrl = (values.length > 18) ? values[18] : "";
378                 if (!host.isEmpty() || !port.isEmpty() || !exclList.isEmpty()) {
379                     profile.proxy =
380                             ProxyInfo.buildDirectProxy(host, port.isEmpty() ?
381                                     0 : Integer.parseInt(port),
382                                     ProxyUtils.exclusionStringAsList(exclList));
383                 } else if (!pacFileUrl.isEmpty()) {
384                     profile.proxy = ProxyInfo.buildPacProxy(Uri.parse(pacFileUrl));
385                 }
386             } // else profile.proxy = null
387 
388             // Either all must be present, or none must be.
389             if (values.length >= 24) {
390                 profile.mAllowedAlgorithms = new ArrayList<>();
391                 for (String algo : Arrays.asList(values[19].split(LIST_DELIMITER))) {
392                     profile.mAllowedAlgorithms.add(URLDecoder.decode(algo, DEFAULT_ENCODING));
393                 }
394 
395                 profile.isBypassable = Boolean.parseBoolean(values[20]);
396                 profile.isMetered = Boolean.parseBoolean(values[21]);
397                 profile.maxMtu = Integer.parseInt(values[22]);
398                 profile.areAuthParamsInline = Boolean.parseBoolean(values[23]);
399             }
400 
401             // isRestrictedToTestNetworks (values[24]) assigned as part of the constructor
402 
403             profile.saveLogin = !profile.username.isEmpty() || !profile.password.isEmpty();
404             return profile;
405         } catch (Exception e) {
406             Log.d(TAG, "Got exception in decode.", e);
407             // ignore
408         }
409         return null;
410     }
411 
412     /**
413      * Encodes a VpnProfile instance to a byte array for storage.
414      *
415      * <p>See {@link #decode(String, byte[])}
416      */
encode()417     public byte[] encode() {
418         StringBuilder builder = new StringBuilder(name);
419         builder.append(VALUE_DELIMITER).append(type);
420         builder.append(VALUE_DELIMITER).append(server);
421         builder.append(VALUE_DELIMITER).append(saveLogin ? username : "");
422         builder.append(VALUE_DELIMITER).append(saveLogin ? password : "");
423         builder.append(VALUE_DELIMITER).append(dnsServers);
424         builder.append(VALUE_DELIMITER).append(searchDomains);
425         builder.append(VALUE_DELIMITER).append(routes);
426         builder.append(VALUE_DELIMITER).append(mppe);
427         builder.append(VALUE_DELIMITER).append(l2tpSecret);
428         builder.append(VALUE_DELIMITER).append(ipsecIdentifier);
429         builder.append(VALUE_DELIMITER).append(ipsecSecret);
430         builder.append(VALUE_DELIMITER).append(ipsecUserCert);
431         builder.append(VALUE_DELIMITER).append(ipsecCaCert);
432         builder.append(VALUE_DELIMITER).append(ipsecServerCert);
433         if (proxy != null) {
434             builder.append(VALUE_DELIMITER).append(proxy.getHost() != null ? proxy.getHost() : "");
435             builder.append(VALUE_DELIMITER).append(proxy.getPort());
436             builder.append(VALUE_DELIMITER)
437                     .append(
438                             ProxyUtils.exclusionListAsString(proxy.getExclusionList()) != null
439                                     ? ProxyUtils.exclusionListAsString(proxy.getExclusionList())
440                                     : "");
441             builder.append(VALUE_DELIMITER).append(proxy.getPacFileUrl().toString());
442         } else {
443             builder.append(ENCODED_NULL_PROXY_INFO);
444         }
445 
446         final List<String> encodedAlgoNames = new ArrayList<>();
447 
448         try {
449             for (String algo : mAllowedAlgorithms) {
450                 encodedAlgoNames.add(URLEncoder.encode(algo, DEFAULT_ENCODING));
451             }
452         } catch (UnsupportedEncodingException e) {
453             // Unexpected error
454             throw new IllegalStateException("Failed to encode algorithms.", e);
455         }
456 
457         builder.append(VALUE_DELIMITER).append(String.join(LIST_DELIMITER, encodedAlgoNames));
458 
459         builder.append(VALUE_DELIMITER).append(isBypassable);
460         builder.append(VALUE_DELIMITER).append(isMetered);
461         builder.append(VALUE_DELIMITER).append(maxMtu);
462         builder.append(VALUE_DELIMITER).append(areAuthParamsInline);
463         builder.append(VALUE_DELIMITER).append(isRestrictedToTestNetworks);
464 
465         builder.append(VALUE_DELIMITER).append(excludeLocalRoutes);
466         builder.append(VALUE_DELIMITER).append(requiresInternetValidation);
467 
468         if (ikeTunConnParams != null) {
469             final PersistableBundle bundle =
470                     TunnelConnectionParamsUtils.toPersistableBundle(ikeTunConnParams);
471             final Parcel parcel = Parcel.obtain();
472             parcel.writeValue(bundle);
473             final byte[] bytes = parcel.marshall();
474             builder.append(VALUE_DELIMITER).append(HexDump.toHexString(bytes));
475         } else {
476             builder.append(VALUE_DELIMITER).append("");
477         }
478         builder.append(VALUE_DELIMITER).append(automaticNattKeepaliveTimerEnabled);
479         builder.append(VALUE_DELIMITER).append(automaticIpVersionSelectionEnabled);
480 
481         return builder.toString().getBytes(StandardCharsets.UTF_8);
482     }
483 
484     /** Checks if this profile specifies a LegacyVpn type. */
isLegacyType(int type)485     public static boolean isLegacyType(int type) {
486         switch (type) {
487             case VpnProfile.TYPE_PPTP:
488             case VpnProfile.TYPE_L2TP_IPSEC_PSK:
489             case VpnProfile.TYPE_L2TP_IPSEC_RSA:
490             case VpnProfile.TYPE_IPSEC_XAUTH_PSK:
491             case VpnProfile.TYPE_IPSEC_XAUTH_RSA:
492             case VpnProfile.TYPE_IPSEC_HYBRID_RSA:
493                 return true;
494             default:
495                 return false;
496         }
497     }
498 
isValidLockdownLegacyVpnProfile()499     private boolean isValidLockdownLegacyVpnProfile() {
500         return isLegacyType(type) && isServerAddressNumeric() && hasDns()
501                 && areDnsAddressesNumeric();
502     }
503 
isValidLockdownPlatformVpnProfile()504     private boolean isValidLockdownPlatformVpnProfile() {
505         return Ikev2VpnProfile.isValidVpnProfile(this);
506     }
507 
508     /**
509      * Tests if profile is valid for lockdown.
510      *
511      * <p>For LegacyVpn profiles, this requires an IPv4 address for both the server and DNS.
512      *
513      * <p>For PlatformVpn profiles, this requires a server, an identifier and the relevant fields to
514      * be non-null.
515      */
isValidLockdownProfile()516     public boolean isValidLockdownProfile() {
517         return isTypeValidForLockdown()
518                 && (isValidLockdownLegacyVpnProfile() || isValidLockdownPlatformVpnProfile());
519     }
520 
521     /** Returns {@code true} if the VPN type is valid for lockdown. */
isTypeValidForLockdown()522     public boolean isTypeValidForLockdown() {
523         // b/7064069: lockdown firewall blocks ports used for PPTP
524         return type != TYPE_PPTP;
525     }
526 
527     /** Returns {@code true} if the server address is numeric, e.g. 8.8.8.8 */
isServerAddressNumeric()528     public boolean isServerAddressNumeric() {
529         try {
530             InetAddress.parseNumericAddress(server);
531         } catch (IllegalArgumentException e) {
532             return false;
533         }
534         return true;
535     }
536 
537     /** Returns {@code true} if one or more DNS servers are specified. */
hasDns()538     public boolean hasDns() {
539         return !TextUtils.isEmpty(dnsServers);
540     }
541 
542     /** Returns {@code true} if all DNS servers have numeric addresses, e.g. 8.8.8.8 */
areDnsAddressesNumeric()543     public boolean areDnsAddressesNumeric() {
544         try {
545             for (String dnsServer : dnsServers.split(" +")) {
546                 InetAddress.parseNumericAddress(dnsServer);
547             }
548         } catch (IllegalArgumentException e) {
549             return false;
550         }
551         return true;
552     }
553 
554     /** Generates a hashcode over the VpnProfile. */
555     @Override
hashCode()556     public int hashCode() {
557         return Objects.hash(
558             key, type, server, username, password, dnsServers, searchDomains, routes, mppe,
559             l2tpSecret, ipsecIdentifier, ipsecSecret, ipsecUserCert, ipsecCaCert, ipsecServerCert,
560             proxy, mAllowedAlgorithms, isBypassable, isMetered, maxMtu, areAuthParamsInline,
561             isRestrictedToTestNetworks, excludeLocalRoutes, requiresInternetValidation,
562             ikeTunConnParams, automaticNattKeepaliveTimerEnabled,
563             automaticIpVersionSelectionEnabled);
564     }
565 
566     /** Checks VPN profiles for interior equality. */
567     @Override
equals(Object obj)568     public boolean equals(Object obj) {
569         if (!(obj instanceof VpnProfile)) {
570             return false;
571         }
572 
573         final VpnProfile other = (VpnProfile) obj;
574         return Objects.equals(key, other.key)
575                 && Objects.equals(name, other.name)
576                 && type == other.type
577                 && Objects.equals(server, other.server)
578                 && Objects.equals(username, other.username)
579                 && Objects.equals(password, other.password)
580                 && Objects.equals(dnsServers, other.dnsServers)
581                 && Objects.equals(searchDomains, other.searchDomains)
582                 && Objects.equals(routes, other.routes)
583                 && mppe == other.mppe
584                 && Objects.equals(l2tpSecret, other.l2tpSecret)
585                 && Objects.equals(ipsecIdentifier, other.ipsecIdentifier)
586                 && Objects.equals(ipsecSecret, other.ipsecSecret)
587                 && Objects.equals(ipsecUserCert, other.ipsecUserCert)
588                 && Objects.equals(ipsecCaCert, other.ipsecCaCert)
589                 && Objects.equals(ipsecServerCert, other.ipsecServerCert)
590                 && Objects.equals(proxy, other.proxy)
591                 && Objects.equals(mAllowedAlgorithms, other.mAllowedAlgorithms)
592                 && isBypassable == other.isBypassable
593                 && isMetered == other.isMetered
594                 && maxMtu == other.maxMtu
595                 && areAuthParamsInline == other.areAuthParamsInline
596                 && isRestrictedToTestNetworks == other.isRestrictedToTestNetworks
597                 && excludeLocalRoutes == other.excludeLocalRoutes
598                 && requiresInternetValidation == other.requiresInternetValidation
599                 && Objects.equals(ikeTunConnParams, other.ikeTunConnParams)
600                 && automaticNattKeepaliveTimerEnabled == other.automaticNattKeepaliveTimerEnabled
601                 && automaticIpVersionSelectionEnabled == other.automaticIpVersionSelectionEnabled;
602     }
603 
604     @NonNull
605     public static final Creator<VpnProfile> CREATOR = new Creator<>() {
606         @Override
607         public VpnProfile createFromParcel(Parcel in) {
608             return new VpnProfile(in);
609         }
610 
611         @Override
612         public VpnProfile[] newArray(int size) {
613             return new VpnProfile[size];
614         }
615     };
616 
617     @Override
describeContents()618     public int describeContents() {
619         return 0;
620     }
621 
622     @Override
clone()623     public VpnProfile clone() {
624         try {
625             return (VpnProfile) super.clone();
626         } catch (CloneNotSupportedException e) {
627             Log.wtf(TAG, e);
628             return null;
629         }
630     }
631 }
632