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.os.Build;
26 import android.os.Parcel;
27 import android.os.Parcelable;
28 import android.text.TextUtils;
29 
30 import com.android.internal.annotations.VisibleForTesting;
31 
32 import java.net.InetAddress;
33 import java.nio.charset.StandardCharsets;
34 import java.util.ArrayList;
35 import java.util.Arrays;
36 import java.util.Collections;
37 import java.util.List;
38 import java.util.Objects;
39 
40 /**
41  * Profile storage class for a platform VPN.
42  *
43  * <p>This class supports both the Legacy VPN, as well as application-configurable platform VPNs
44  * (such as IKEv2/IPsec).
45  *
46  * <p>This class is serialized and deserialized via the {@link #encode()} and {@link #decode()}
47  * functions for persistent storage in the Android Keystore. The encoding is entirely custom, but
48  * must be kept for backward compatibility for devices upgrading between Android versions.
49  *
50  * @hide
51  */
52 public final class VpnProfile implements Cloneable, Parcelable {
53     private static final String TAG = "VpnProfile";
54 
55     @VisibleForTesting static final String VALUE_DELIMITER = "\0";
56     @VisibleForTesting static final String LIST_DELIMITER = ",";
57 
58     // Match these constants with R.array.vpn_types.
59     public static final int TYPE_PPTP = 0;
60     public static final int TYPE_L2TP_IPSEC_PSK = 1;
61     public static final int TYPE_L2TP_IPSEC_RSA = 2;
62     public static final int TYPE_IPSEC_XAUTH_PSK = 3;
63     public static final int TYPE_IPSEC_XAUTH_RSA = 4;
64     public static final int TYPE_IPSEC_HYBRID_RSA = 5;
65     public static final int TYPE_IKEV2_IPSEC_USER_PASS = 6;
66     public static final int TYPE_IKEV2_IPSEC_PSK = 7;
67     public static final int TYPE_IKEV2_IPSEC_RSA = 8;
68     public static final int TYPE_MAX = 8;
69 
70     // Match these constants with R.array.vpn_proxy_settings.
71     public static final int PROXY_NONE = 0;
72     public static final int PROXY_MANUAL = 1;
73 
74     private static final String ENCODED_NULL_PROXY_INFO = "\0\0\0\0";
75 
76     // Entity fields.
77     @UnsupportedAppUsage
78     public final String key;                                   // -1
79 
80     @UnsupportedAppUsage
81     public String name = "";                                   // 0
82 
83     @UnsupportedAppUsage
84     public int type = TYPE_PPTP;                               // 1
85 
86     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
87     public String server = "";                                 // 2
88 
89     @UnsupportedAppUsage
90     public String username = "";                               // 3
91     public String password = "";                               // 4
92     public String dnsServers = "";                             // 5
93     public String searchDomains = "";                          // 6
94     public String routes = "";                                 // 7
95     public boolean mppe = true;                                // 8
96     public String l2tpSecret = "";                             // 9
97     public String ipsecIdentifier = "";                        // 10
98 
99     /**
100      * The RSA private key or pre-shared key used for authentication.
101      *
102      * <p>If areAuthParamsInline is {@code true}, this String will be either:
103      *
104      * <ul>
105      *   <li>If this is an IKEv2 RSA profile: a PKCS#8 encoded {@link java.security.PrivateKey}
106      *   <li>If this is an IKEv2 PSK profile: a string value representing the PSK.
107      * </ul>
108      */
109     public String ipsecSecret = "";                            // 11
110 
111     /**
112      * The RSA certificate to be used for digital signature authentication.
113      *
114      * <p>If areAuthParamsInline is {@code true}, this String will be a pem-encoded {@link
115      * java.security.X509Certificate}
116      */
117     public String ipsecUserCert = "";                          // 12
118 
119     /**
120      * The RSA certificate that should be used to verify the server's end/target certificate.
121      *
122      * <p>If areAuthParamsInline is {@code true}, this String will be a pem-encoded {@link
123      * java.security.X509Certificate}
124      */
125     public String ipsecCaCert = "";                            // 13
126     public String ipsecServerCert = "";                        // 14
127     public ProxyInfo proxy = null;                             // 15~18
128 
129     /**
130      * The list of allowable algorithms.
131      *
132      * <p>This list is validated in the setter to ensure that encoding characters (list, value
133      * delimiters) are not present in the algorithm names. See {@link #validateAllowedAlgorithms()}
134      */
135     private List<String> mAllowedAlgorithms = new ArrayList<>(); // 19
136     public boolean isBypassable = false;                         // 20
137     public boolean isMetered = false;                            // 21
138     public int maxMtu = PlatformVpnProfile.MAX_MTU_DEFAULT;      // 22
139     public boolean areAuthParamsInline = false;                  // 23
140     public final boolean isRestrictedToTestNetworks;             // 24
141 
142     // Helper fields.
143     @UnsupportedAppUsage
144     public transient boolean saveLogin = false;
145 
VpnProfile(String key)146     public VpnProfile(String key) {
147         this(key, false);
148     }
149 
VpnProfile(String key, boolean isRestrictedToTestNetworks)150     public VpnProfile(String key, boolean isRestrictedToTestNetworks) {
151         this.key = key;
152         this.isRestrictedToTestNetworks = isRestrictedToTestNetworks;
153     }
154 
155     @UnsupportedAppUsage
VpnProfile(Parcel in)156     public VpnProfile(Parcel in) {
157         key = in.readString();
158         name = in.readString();
159         type = in.readInt();
160         server = in.readString();
161         username = in.readString();
162         password = in.readString();
163         dnsServers = in.readString();
164         searchDomains = in.readString();
165         routes = in.readString();
166         mppe = in.readInt() != 0;
167         l2tpSecret = in.readString();
168         ipsecIdentifier = in.readString();
169         ipsecSecret = in.readString();
170         ipsecUserCert = in.readString();
171         ipsecCaCert = in.readString();
172         ipsecServerCert = in.readString();
173         saveLogin = in.readInt() != 0;
174         proxy = in.readParcelable(null);
175         mAllowedAlgorithms = new ArrayList<>();
176         in.readList(mAllowedAlgorithms, null);
177         isBypassable = in.readBoolean();
178         isMetered = in.readBoolean();
179         maxMtu = in.readInt();
180         areAuthParamsInline = in.readBoolean();
181         isRestrictedToTestNetworks = in.readBoolean();
182     }
183 
184     /**
185      * Retrieves the list of allowed algorithms.
186      *
187      * <p>The contained elements are as listed in {@link IpSecAlgorithm}
188      */
getAllowedAlgorithms()189     public List<String> getAllowedAlgorithms() {
190         return Collections.unmodifiableList(mAllowedAlgorithms);
191     }
192 
193     /**
194      * Validates and sets the list of algorithms that can be used for the IPsec transforms.
195      *
196      * @param allowedAlgorithms the list of allowable algorithms, as listed in {@link
197      *     IpSecAlgorithm}.
198      * @throws IllegalArgumentException if any delimiters are used in algorithm names. See {@link
199      *     #VALUE_DELIMITER} and {@link LIST_DELIMITER}.
200      */
setAllowedAlgorithms(List<String> allowedAlgorithms)201     public void setAllowedAlgorithms(List<String> allowedAlgorithms) {
202         validateAllowedAlgorithms(allowedAlgorithms);
203         mAllowedAlgorithms = allowedAlgorithms;
204     }
205 
206     @Override
writeToParcel(Parcel out, int flags)207     public void writeToParcel(Parcel out, int flags) {
208         out.writeString(key);
209         out.writeString(name);
210         out.writeInt(type);
211         out.writeString(server);
212         out.writeString(username);
213         out.writeString(password);
214         out.writeString(dnsServers);
215         out.writeString(searchDomains);
216         out.writeString(routes);
217         out.writeInt(mppe ? 1 : 0);
218         out.writeString(l2tpSecret);
219         out.writeString(ipsecIdentifier);
220         out.writeString(ipsecSecret);
221         out.writeString(ipsecUserCert);
222         out.writeString(ipsecCaCert);
223         out.writeString(ipsecServerCert);
224         out.writeInt(saveLogin ? 1 : 0);
225         out.writeParcelable(proxy, flags);
226         out.writeList(mAllowedAlgorithms);
227         out.writeBoolean(isBypassable);
228         out.writeBoolean(isMetered);
229         out.writeInt(maxMtu);
230         out.writeBoolean(areAuthParamsInline);
231         out.writeBoolean(isRestrictedToTestNetworks);
232     }
233 
234     /**
235      * Decodes a VpnProfile instance from the encoded byte array.
236      *
237      * <p>See {@link #encode()}
238      */
239     @UnsupportedAppUsage
decode(String key, byte[] value)240     public static VpnProfile decode(String key, byte[] value) {
241         try {
242             if (key == null) {
243                 return null;
244             }
245 
246             String[] values = new String(value, StandardCharsets.UTF_8).split(VALUE_DELIMITER, -1);
247             // Acceptable numbers of values are:
248             // 14-19: Standard profile, with option for serverCert, proxy
249             // 24: Standard profile with serverCert, proxy and platform-VPN parameters
250             // 25: Standard profile with platform-VPN parameters and isRestrictedToTestNetworks
251             if ((values.length < 14 || values.length > 19)
252                     && values.length != 24 && values.length != 25) {
253                 return null;
254             }
255 
256             final boolean isRestrictedToTestNetworks;
257             if (values.length >= 25) {
258                 isRestrictedToTestNetworks = Boolean.parseBoolean(values[24]);
259             } else {
260                 isRestrictedToTestNetworks = false;
261             }
262 
263             VpnProfile profile = new VpnProfile(key, isRestrictedToTestNetworks);
264             profile.name = values[0];
265             profile.type = Integer.parseInt(values[1]);
266             if (profile.type < 0 || profile.type > TYPE_MAX) {
267                 return null;
268             }
269             profile.server = values[2];
270             profile.username = values[3];
271             profile.password = values[4];
272             profile.dnsServers = values[5];
273             profile.searchDomains = values[6];
274             profile.routes = values[7];
275             profile.mppe = Boolean.parseBoolean(values[8]);
276             profile.l2tpSecret = values[9];
277             profile.ipsecIdentifier = values[10];
278             profile.ipsecSecret = values[11];
279             profile.ipsecUserCert = values[12];
280             profile.ipsecCaCert = values[13];
281             profile.ipsecServerCert = (values.length > 14) ? values[14] : "";
282             if (values.length > 15) {
283                 String host = (values.length > 15) ? values[15] : "";
284                 String port = (values.length > 16) ? values[16] : "";
285                 String exclList = (values.length > 17) ? values[17] : "";
286                 String pacFileUrl = (values.length > 18) ? values[18] : "";
287                 if (!host.isEmpty() || !port.isEmpty() || !exclList.isEmpty()) {
288                     profile.proxy = new ProxyInfo(host, port.isEmpty() ?
289                             0 : Integer.parseInt(port), exclList);
290                 } else if (!pacFileUrl.isEmpty()) {
291                     profile.proxy = new ProxyInfo(Uri.parse(pacFileUrl));
292                 }
293             } // else profile.proxy = null
294 
295             // Either all must be present, or none must be.
296             if (values.length >= 24) {
297                 profile.mAllowedAlgorithms = Arrays.asList(values[19].split(LIST_DELIMITER));
298                 profile.isBypassable = Boolean.parseBoolean(values[20]);
299                 profile.isMetered = Boolean.parseBoolean(values[21]);
300                 profile.maxMtu = Integer.parseInt(values[22]);
301                 profile.areAuthParamsInline = Boolean.parseBoolean(values[23]);
302             }
303 
304             // isRestrictedToTestNetworks (values[24]) assigned as part of the constructor
305 
306             profile.saveLogin = !profile.username.isEmpty() || !profile.password.isEmpty();
307             return profile;
308         } catch (Exception e) {
309             // ignore
310         }
311         return null;
312     }
313 
314     /**
315      * Encodes a VpnProfile instance to a byte array for storage.
316      *
317      * <p>See {@link #decode(String, byte[])}
318      */
encode()319     public byte[] encode() {
320         StringBuilder builder = new StringBuilder(name);
321         builder.append(VALUE_DELIMITER).append(type);
322         builder.append(VALUE_DELIMITER).append(server);
323         builder.append(VALUE_DELIMITER).append(saveLogin ? username : "");
324         builder.append(VALUE_DELIMITER).append(saveLogin ? password : "");
325         builder.append(VALUE_DELIMITER).append(dnsServers);
326         builder.append(VALUE_DELIMITER).append(searchDomains);
327         builder.append(VALUE_DELIMITER).append(routes);
328         builder.append(VALUE_DELIMITER).append(mppe);
329         builder.append(VALUE_DELIMITER).append(l2tpSecret);
330         builder.append(VALUE_DELIMITER).append(ipsecIdentifier);
331         builder.append(VALUE_DELIMITER).append(ipsecSecret);
332         builder.append(VALUE_DELIMITER).append(ipsecUserCert);
333         builder.append(VALUE_DELIMITER).append(ipsecCaCert);
334         builder.append(VALUE_DELIMITER).append(ipsecServerCert);
335         if (proxy != null) {
336             builder.append(VALUE_DELIMITER).append(proxy.getHost() != null ? proxy.getHost() : "");
337             builder.append(VALUE_DELIMITER).append(proxy.getPort());
338             builder.append(VALUE_DELIMITER)
339                     .append(
340                             proxy.getExclusionListAsString() != null
341                                     ? proxy.getExclusionListAsString()
342                                     : "");
343             builder.append(VALUE_DELIMITER).append(proxy.getPacFileUrl().toString());
344         } else {
345             builder.append(ENCODED_NULL_PROXY_INFO);
346         }
347 
348         builder.append(VALUE_DELIMITER).append(String.join(LIST_DELIMITER, mAllowedAlgorithms));
349         builder.append(VALUE_DELIMITER).append(isBypassable);
350         builder.append(VALUE_DELIMITER).append(isMetered);
351         builder.append(VALUE_DELIMITER).append(maxMtu);
352         builder.append(VALUE_DELIMITER).append(areAuthParamsInline);
353         builder.append(VALUE_DELIMITER).append(isRestrictedToTestNetworks);
354 
355         return builder.toString().getBytes(StandardCharsets.UTF_8);
356     }
357 
358     /** Checks if this profile specifies a LegacyVpn type. */
isLegacyType(int type)359     public static boolean isLegacyType(int type) {
360         switch (type) {
361             case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS: // fall through
362             case VpnProfile.TYPE_IKEV2_IPSEC_RSA: // fall through
363             case VpnProfile.TYPE_IKEV2_IPSEC_PSK:
364                 return false;
365             default:
366                 return true;
367         }
368     }
369 
isValidLockdownLegacyVpnProfile()370     private boolean isValidLockdownLegacyVpnProfile() {
371         return isLegacyType(type) && isServerAddressNumeric() && hasDns()
372                 && areDnsAddressesNumeric();
373     }
374 
isValidLockdownPlatformVpnProfile()375     private boolean isValidLockdownPlatformVpnProfile() {
376         return Ikev2VpnProfile.isValidVpnProfile(this);
377     }
378 
379     /**
380      * Tests if profile is valid for lockdown.
381      *
382      * <p>For LegacyVpn profiles, this requires an IPv4 address for both the server and DNS.
383      *
384      * <p>For PlatformVpn profiles, this requires a server, an identifier and the relevant fields to
385      * be non-null.
386      */
isValidLockdownProfile()387     public boolean isValidLockdownProfile() {
388         return isTypeValidForLockdown()
389                 && (isValidLockdownLegacyVpnProfile() || isValidLockdownPlatformVpnProfile());
390     }
391 
392     /** Returns {@code true} if the VPN type is valid for lockdown. */
isTypeValidForLockdown()393     public boolean isTypeValidForLockdown() {
394         // b/7064069: lockdown firewall blocks ports used for PPTP
395         return type != TYPE_PPTP;
396     }
397 
398     /** Returns {@code true} if the server address is numeric, e.g. 8.8.8.8 */
isServerAddressNumeric()399     public boolean isServerAddressNumeric() {
400         try {
401             InetAddress.parseNumericAddress(server);
402         } catch (IllegalArgumentException e) {
403             return false;
404         }
405         return true;
406     }
407 
408     /** Returns {@code true} if one or more DNS servers are specified. */
hasDns()409     public boolean hasDns() {
410         return !TextUtils.isEmpty(dnsServers);
411     }
412 
413     /** Returns {@code true} if all DNS servers have numeric addresses, e.g. 8.8.8.8 */
areDnsAddressesNumeric()414     public boolean areDnsAddressesNumeric() {
415         try {
416             for (String dnsServer : dnsServers.split(" +")) {
417                 InetAddress.parseNumericAddress(dnsServer);
418             }
419         } catch (IllegalArgumentException e) {
420             return false;
421         }
422         return true;
423     }
424 
425     /**
426      * Validates that the provided list of algorithms does not contain illegal characters.
427      *
428      * @param allowedAlgorithms The list to be validated
429      */
validateAllowedAlgorithms(List<String> allowedAlgorithms)430     public static void validateAllowedAlgorithms(List<String> allowedAlgorithms) {
431         for (final String alg : allowedAlgorithms) {
432             if (alg.contains(VALUE_DELIMITER) || alg.contains(LIST_DELIMITER)) {
433                 throw new IllegalArgumentException(
434                         "Algorithm contained illegal ('\0' or ',') character");
435             }
436         }
437     }
438 
439     /** Generates a hashcode over the VpnProfile. */
440     @Override
hashCode()441     public int hashCode() {
442         return Objects.hash(
443             key, type, server, username, password, dnsServers, searchDomains, routes, mppe,
444             l2tpSecret, ipsecIdentifier, ipsecSecret, ipsecUserCert, ipsecCaCert, ipsecServerCert,
445             proxy, mAllowedAlgorithms, isBypassable, isMetered, maxMtu, areAuthParamsInline,
446             isRestrictedToTestNetworks);
447     }
448 
449     /** Checks VPN profiles for interior equality. */
450     @Override
equals(Object obj)451     public boolean equals(Object obj) {
452         if (!(obj instanceof VpnProfile)) {
453             return false;
454         }
455 
456         final VpnProfile other = (VpnProfile) obj;
457         return Objects.equals(key, other.key)
458                 && Objects.equals(name, other.name)
459                 && type == other.type
460                 && Objects.equals(server, other.server)
461                 && Objects.equals(username, other.username)
462                 && Objects.equals(password, other.password)
463                 && Objects.equals(dnsServers, other.dnsServers)
464                 && Objects.equals(searchDomains, other.searchDomains)
465                 && Objects.equals(routes, other.routes)
466                 && mppe == other.mppe
467                 && Objects.equals(l2tpSecret, other.l2tpSecret)
468                 && Objects.equals(ipsecIdentifier, other.ipsecIdentifier)
469                 && Objects.equals(ipsecSecret, other.ipsecSecret)
470                 && Objects.equals(ipsecUserCert, other.ipsecUserCert)
471                 && Objects.equals(ipsecCaCert, other.ipsecCaCert)
472                 && Objects.equals(ipsecServerCert, other.ipsecServerCert)
473                 && Objects.equals(proxy, other.proxy)
474                 && Objects.equals(mAllowedAlgorithms, other.mAllowedAlgorithms)
475                 && isBypassable == other.isBypassable
476                 && isMetered == other.isMetered
477                 && maxMtu == other.maxMtu
478                 && areAuthParamsInline == other.areAuthParamsInline
479                 && isRestrictedToTestNetworks == other.isRestrictedToTestNetworks;
480     }
481 
482     @NonNull
483     public static final Creator<VpnProfile> CREATOR = new Creator<VpnProfile>() {
484         @Override
485         public VpnProfile createFromParcel(Parcel in) {
486             return new VpnProfile(in);
487         }
488 
489         @Override
490         public VpnProfile[] newArray(int size) {
491             return new VpnProfile[size];
492         }
493     };
494 
495     @Override
describeContents()496     public int describeContents() {
497         return 0;
498     }
499 }
500