1 /*
2  * Copyright 2020 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 package com.android.server.blob;
17 
18 import static android.app.blob.XmlTags.ATTR_CERTIFICATE;
19 import static android.app.blob.XmlTags.ATTR_PACKAGE;
20 import static android.app.blob.XmlTags.ATTR_TYPE;
21 import static android.app.blob.XmlTags.TAG_ALLOWED_PACKAGE;
22 
23 import android.annotation.IntDef;
24 import android.annotation.NonNull;
25 import android.content.Context;
26 import android.content.pm.PackageManager;
27 import android.util.ArraySet;
28 import android.util.Base64;
29 import android.util.DebugUtils;
30 import android.util.IndentingPrintWriter;
31 
32 import com.android.internal.util.XmlUtils;
33 
34 import org.xmlpull.v1.XmlPullParser;
35 import org.xmlpull.v1.XmlPullParserException;
36 import org.xmlpull.v1.XmlSerializer;
37 
38 import java.io.IOException;
39 import java.lang.annotation.Retention;
40 import java.lang.annotation.RetentionPolicy;
41 import java.util.Arrays;
42 import java.util.Objects;
43 
44 /**
45  * Class for representing how a blob can be shared.
46  *
47  * Note that this class is not thread-safe, callers need to take care of synchronizing access.
48  */
49 class BlobAccessMode {
50     @Retention(RetentionPolicy.SOURCE)
51     @IntDef(flag = true, value = {
52             ACCESS_TYPE_PRIVATE,
53             ACCESS_TYPE_PUBLIC,
54             ACCESS_TYPE_SAME_SIGNATURE,
55             ACCESS_TYPE_ALLOWLIST,
56     })
57     @interface AccessType {}
58     public static final int ACCESS_TYPE_PRIVATE = 1 << 0;
59     public static final int ACCESS_TYPE_PUBLIC = 1 << 1;
60     public static final int ACCESS_TYPE_SAME_SIGNATURE = 1 << 2;
61     public static final int ACCESS_TYPE_ALLOWLIST = 1 << 3;
62 
63     private int mAccessType = ACCESS_TYPE_PRIVATE;
64 
65     private final ArraySet<PackageIdentifier> mAllowedPackages = new ArraySet<>();
66 
allow(BlobAccessMode other)67     void allow(BlobAccessMode other) {
68         if ((other.mAccessType & ACCESS_TYPE_ALLOWLIST) != 0) {
69             mAllowedPackages.addAll(other.mAllowedPackages);
70         }
71         mAccessType |= other.mAccessType;
72     }
73 
allowPublicAccess()74     void allowPublicAccess() {
75         mAccessType |= ACCESS_TYPE_PUBLIC;
76     }
77 
allowSameSignatureAccess()78     void allowSameSignatureAccess() {
79         mAccessType |= ACCESS_TYPE_SAME_SIGNATURE;
80     }
81 
allowPackageAccess(@onNull String packageName, @NonNull byte[] certificate)82     void allowPackageAccess(@NonNull String packageName, @NonNull byte[] certificate) {
83         mAccessType |= ACCESS_TYPE_ALLOWLIST;
84         mAllowedPackages.add(PackageIdentifier.create(packageName, certificate));
85     }
86 
isPublicAccessAllowed()87     boolean isPublicAccessAllowed() {
88         return (mAccessType & ACCESS_TYPE_PUBLIC) != 0;
89     }
90 
isSameSignatureAccessAllowed()91     boolean isSameSignatureAccessAllowed() {
92         return (mAccessType & ACCESS_TYPE_SAME_SIGNATURE) != 0;
93     }
94 
isPackageAccessAllowed(@onNull String packageName, @NonNull byte[] certificate)95     boolean isPackageAccessAllowed(@NonNull String packageName, @NonNull byte[] certificate) {
96         if ((mAccessType & ACCESS_TYPE_ALLOWLIST) == 0) {
97             return false;
98         }
99         return mAllowedPackages.contains(PackageIdentifier.create(packageName, certificate));
100     }
101 
isAccessAllowedForCaller(Context context, @NonNull String callingPackage, @NonNull String committerPackage)102     boolean isAccessAllowedForCaller(Context context,
103             @NonNull String callingPackage, @NonNull String committerPackage) {
104         if ((mAccessType & ACCESS_TYPE_PUBLIC) != 0) {
105             return true;
106         }
107 
108         final PackageManager pm = context.getPackageManager();
109         if ((mAccessType & ACCESS_TYPE_SAME_SIGNATURE) != 0) {
110             if (pm.checkSignatures(committerPackage, callingPackage)
111                     == PackageManager.SIGNATURE_MATCH) {
112                 return true;
113             }
114         }
115 
116         if ((mAccessType & ACCESS_TYPE_ALLOWLIST) != 0) {
117             for (int i = 0; i < mAllowedPackages.size(); ++i) {
118                 final PackageIdentifier packageIdentifier = mAllowedPackages.valueAt(i);
119                 if (packageIdentifier.packageName.equals(callingPackage)
120                         && pm.hasSigningCertificate(callingPackage, packageIdentifier.certificate,
121                                 PackageManager.CERT_INPUT_SHA256)) {
122                     return true;
123                 }
124             }
125         }
126 
127         return false;
128     }
129 
getAccessType()130     int getAccessType() {
131         return mAccessType;
132     }
133 
getAllowedPackagesCount()134     int getAllowedPackagesCount() {
135         return mAllowedPackages.size();
136     }
137 
dump(IndentingPrintWriter fout)138     void dump(IndentingPrintWriter fout) {
139         fout.println("accessType: " + DebugUtils.flagsToString(
140                 BlobAccessMode.class, "ACCESS_TYPE_", mAccessType));
141         fout.print("Explicitly allowed pkgs:");
142         if (mAllowedPackages.isEmpty()) {
143             fout.println(" (Empty)");
144         } else {
145             fout.increaseIndent();
146             for (int i = 0, count = mAllowedPackages.size(); i < count; ++i) {
147                 fout.println(mAllowedPackages.valueAt(i).toString());
148             }
149             fout.decreaseIndent();
150         }
151     }
152 
writeToXml(@onNull XmlSerializer out)153     void writeToXml(@NonNull XmlSerializer out) throws IOException {
154         XmlUtils.writeIntAttribute(out, ATTR_TYPE, mAccessType);
155         for (int i = 0, count = mAllowedPackages.size(); i < count; ++i) {
156             out.startTag(null, TAG_ALLOWED_PACKAGE);
157             final PackageIdentifier packageIdentifier = mAllowedPackages.valueAt(i);
158             XmlUtils.writeStringAttribute(out, ATTR_PACKAGE, packageIdentifier.packageName);
159             XmlUtils.writeByteArrayAttribute(out, ATTR_CERTIFICATE, packageIdentifier.certificate);
160             out.endTag(null, TAG_ALLOWED_PACKAGE);
161         }
162     }
163 
164     @NonNull
createFromXml(@onNull XmlPullParser in)165     static BlobAccessMode createFromXml(@NonNull XmlPullParser in)
166             throws IOException, XmlPullParserException {
167         final BlobAccessMode blobAccessMode = new BlobAccessMode();
168 
169         final int accessType = XmlUtils.readIntAttribute(in, ATTR_TYPE);
170         blobAccessMode.mAccessType = accessType;
171 
172         final int depth = in.getDepth();
173         while (XmlUtils.nextElementWithin(in, depth)) {
174             if (TAG_ALLOWED_PACKAGE.equals(in.getName())) {
175                 final String packageName = XmlUtils.readStringAttribute(in, ATTR_PACKAGE);
176                 final byte[] certificate = XmlUtils.readByteArrayAttribute(in, ATTR_CERTIFICATE);
177                 blobAccessMode.allowPackageAccess(packageName, certificate);
178             }
179         }
180         return blobAccessMode;
181     }
182 
183     private static final class PackageIdentifier {
184         public final String packageName;
185         public final byte[] certificate;
186 
PackageIdentifier(@onNull String packageName, @NonNull byte[] certificate)187         private PackageIdentifier(@NonNull String packageName, @NonNull byte[] certificate) {
188             this.packageName = packageName;
189             this.certificate = certificate;
190         }
191 
create(@onNull String packageName, @NonNull byte[] certificate)192         public static PackageIdentifier create(@NonNull String packageName,
193                 @NonNull byte[] certificate) {
194             return new PackageIdentifier(packageName, certificate);
195         }
196 
197         @Override
equals(Object obj)198         public boolean equals(Object obj) {
199             if (this == obj) {
200                 return true;
201             }
202             if (obj == null || !(obj instanceof PackageIdentifier)) {
203                 return false;
204             }
205             final PackageIdentifier other = (PackageIdentifier) obj;
206             return this.packageName.equals(other.packageName)
207                     && Arrays.equals(this.certificate, other.certificate);
208         }
209 
210         @Override
hashCode()211         public int hashCode() {
212             return Objects.hash(packageName, Arrays.hashCode(certificate));
213         }
214 
215         @Override
toString()216         public String toString() {
217             return "[" + packageName + ", "
218                     + Base64.encodeToString(certificate, Base64.NO_WRAP) + "]";
219         }
220     }
221 }
222