1 /*
2  * Copyright (C) 2022 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.pm.pkg.component;
18 
19 
20 import android.content.pm.parsing.result.ParseInput;
21 import android.content.pm.parsing.result.ParseResult;
22 import android.content.res.Resources;
23 import android.content.res.TypedArray;
24 import android.content.res.XmlResourceParser;
25 import android.os.Build;
26 import android.util.ArraySet;
27 
28 import com.android.internal.R;
29 import com.android.internal.pm.pkg.parsing.ParsingPackage;
30 import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
31 
32 import org.xmlpull.v1.XmlPullParser;
33 import org.xmlpull.v1.XmlPullParserException;
34 
35 import java.io.IOException;
36 import java.util.Set;
37 
38 /**
39  * Utility methods for handling the tag {@code <install-constraints/>}
40  *
41  * @hide
42  */
43 public class InstallConstraintsTagParser {
44 
45     private static final String TAG_FINGERPRINT_PREFIX = "fingerprint-prefix";
46 
47     /**
48      * @hide
49      */
parseInstallConstraints( ParseInput input, ParsingPackage pkg, Resources res, XmlResourceParser parser, Set<String> allowlist)50     public static ParseResult<ParsingPackage> parseInstallConstraints(
51             ParseInput input, ParsingPackage pkg, Resources res, XmlResourceParser parser,
52             Set<String> allowlist) throws XmlPullParserException, IOException {
53         if (!allowlist.contains(pkg.getPackageName())) {
54             return input.skip("install-constraints cannot be used by this package");
55         }
56 
57         ParseResult<Set<String>> prefixes = parseFingerprintPrefixes(input, res, parser);
58         if (prefixes.isSuccess()) {
59             if (validateFingerprintPrefixes(prefixes.getResult())) {
60                 return input.success(pkg);
61             } else {
62                 return input.skip(
63                         "Install of this package is restricted on this device; device fingerprint"
64                                 + " does not start with one of the allowed prefixes");
65             }
66         }
67         return input.skip(prefixes.getErrorMessage());
68     }
69 
parseFingerprintPrefixes( ParseInput input, Resources res, XmlResourceParser parser)70     private static ParseResult<Set<String>> parseFingerprintPrefixes(
71             ParseInput input, Resources res, XmlResourceParser parser)
72             throws XmlPullParserException, IOException {
73         Set<String> prefixes = new ArraySet<>();
74         int type;
75         while (true) {
76             // move to the tag that contains the next prefix
77             type = parser.next();
78             if (type == XmlPullParser.END_TAG) {
79                 if (prefixes.size() == 0) {
80                     return input.error("install-constraints must contain at least one constraint");
81                 }
82                 return input.success(prefixes);
83             } else if (type == XmlPullParser.START_TAG) {
84                 if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) {
85                     continue;
86                 }
87                 if (parser.getName().equals(TAG_FINGERPRINT_PREFIX)) {
88                     ParseResult<String> parsedPrefix =
89                             readFingerprintPrefixValue(input, res, parser);
90                     if (parsedPrefix.isSuccess()) {
91                         prefixes.add(parsedPrefix.getResult());
92                     } else {
93                         return input.error(parsedPrefix.getErrorMessage());
94                     }
95                 } else {
96                     return input.error("Unexpected tag: " + parser.getName());
97                 }
98 
99                 // consume the end tag of this attribute
100                 type = parser.next();
101                 if (type != XmlPullParser.END_TAG) {
102                     return input.error("Expected end tag; instead got " + type);
103                 }
104             }
105         }
106     }
107 
readFingerprintPrefixValue(ParseInput input, Resources res, XmlResourceParser parser)108     private static ParseResult<String> readFingerprintPrefixValue(ParseInput input, Resources res,
109             XmlResourceParser parser) {
110         TypedArray sa = res.obtainAttributes(parser,
111                 R.styleable.AndroidManifestInstallConstraintsFingerprintPrefix);
112         try {
113             String value = sa.getString(
114                     R.styleable.AndroidManifestInstallConstraintsFingerprintPrefix_value);
115             if (value == null) {
116                 return input.error("Failed to specify prefix value");
117             }
118             return input.success(value);
119         } finally {
120             sa.recycle();
121         }
122     }
123 
validateFingerprintPrefixes(Set<String> prefixes)124     private static boolean validateFingerprintPrefixes(Set<String> prefixes) {
125         String fingerprint = Build.FINGERPRINT;
126         for (String prefix : prefixes) {
127             if (fingerprint.startsWith(prefix)) {
128                 return true;
129             }
130         }
131         return false;
132     }
133 }
134