1 /*
2  * Copyright (C) 2014 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.example.android.mediabrowserservice;
17 
18 import android.content.Context;
19 import android.content.pm.PackageInfo;
20 import android.content.pm.PackageManager;
21 import android.content.res.XmlResourceParser;
22 import android.os.Process;
23 import android.util.Base64;
24 
25 import com.example.android.mediabrowserservice.utils.LogHelper;
26 
27 import org.xmlpull.v1.XmlPullParserException;
28 
29 import java.io.IOException;
30 import java.util.ArrayList;
31 import java.util.HashMap;
32 import java.util.Map;
33 
34 /**
35  * Validates that the calling package is authorized to browse a
36  * {@link android.service.media.MediaBrowserService}.
37  *
38  * The list of allowed signing certificates and their corresponding package names is defined in
39  * res/xml/allowed_media_browser_callers.xml.
40  */
41 public class PackageValidator {
42     private static final String TAG = LogHelper.makeLogTag(PackageValidator.class);
43 
44     /**
45      * Map allowed callers' certificate keys to the expected caller information.
46      *
47      */
48     private final Map<String, ArrayList<CallerInfo>> mValidCertificates;
49 
PackageValidator(Context ctx)50     public PackageValidator(Context ctx) {
51         mValidCertificates = readValidCertificates(ctx.getResources().getXml(
52             R.xml.allowed_media_browser_callers));
53     }
54 
readValidCertificates(XmlResourceParser parser)55     private Map<String, ArrayList<CallerInfo>> readValidCertificates(XmlResourceParser parser) {
56         HashMap<String, ArrayList<CallerInfo>> validCertificates = new HashMap<>();
57         try {
58             int eventType = parser.next();
59             while (eventType != XmlResourceParser.END_DOCUMENT) {
60                 if (eventType == XmlResourceParser.START_TAG
61                         && parser.getName().equals("signing_certificate")) {
62 
63                     String name = parser.getAttributeValue(null, "name");
64                     String packageName = parser.getAttributeValue(null, "package");
65                     boolean isRelease = parser.getAttributeBooleanValue(null, "release", false);
66                     String certificate = parser.nextText().replaceAll("\\s|\\n", "");
67 
68                     CallerInfo info = new CallerInfo(name, packageName, isRelease, certificate);
69 
70                     ArrayList<CallerInfo> infos = validCertificates.get(certificate);
71                     if (infos == null) {
72                         infos = new ArrayList<>();
73                         validCertificates.put(certificate, infos);
74                     }
75                     LogHelper.v(TAG, "Adding allowed caller: ", info.name,
76                         " package=", info.packageName, " release=", info.release,
77                         " certificate=", certificate);
78                     infos.add(info);
79                 }
80                 eventType = parser.next();
81             }
82         } catch (XmlPullParserException | IOException e) {
83             LogHelper.e(TAG, e, "Could not read allowed callers from XML.");
84         }
85         return validCertificates;
86     }
87 
88     /**
89      * @return false if the caller is not authorized to get data from this MediaBrowserService
90      */
91     @SuppressWarnings("BooleanMethodIsAlwaysInverted")
isCallerAllowed(Context context, String callingPackage, int callingUid)92     public boolean isCallerAllowed(Context context, String callingPackage, int callingUid) {
93         // Always allow calls from the framework, self app or development environment.
94         if (Process.SYSTEM_UID == callingUid || Process.myUid() == callingUid) {
95             return true;
96         }
97         PackageManager packageManager = context.getPackageManager();
98         PackageInfo packageInfo;
99         try {
100             packageInfo = packageManager.getPackageInfo(
101                     callingPackage, PackageManager.GET_SIGNATURES);
102         } catch (PackageManager.NameNotFoundException e) {
103             LogHelper.w(TAG, e, "Package manager can't find package: ", callingPackage);
104             return false;
105         }
106         if (packageInfo.signatures.length != 1) {
107             LogHelper.w(TAG, "Caller has more than one signature certificate!");
108             return false;
109         }
110         String signature = Base64.encodeToString(
111             packageInfo.signatures[0].toByteArray(), Base64.NO_WRAP);
112 
113         // Test for known signatures:
114         ArrayList<CallerInfo> validCallers = mValidCertificates.get(signature);
115         if (validCallers == null) {
116             LogHelper.v(TAG, "Signature for caller ", callingPackage, " is not valid: \n"
117                 , signature);
118             if (mValidCertificates.isEmpty()) {
119                 LogHelper.w(TAG, "The list of valid certificates is empty. Either your file ",
120                         "res/xml/allowed_media_browser_callers.xml is empty or there was an error ",
121                         "while reading it. Check previous log messages.");
122             }
123             return false;
124         }
125 
126         // Check if the package name is valid for the certificate:
127         StringBuffer expectedPackages = new StringBuffer();
128         for (CallerInfo info: validCallers) {
129             if (callingPackage.equals(info.packageName)) {
130                 LogHelper.v(TAG, "Valid caller: ", info.name, "  package=", info.packageName,
131                     " release=", info.release);
132                 return true;
133             }
134             expectedPackages.append(info.packageName).append(' ');
135         }
136 
137         LogHelper.i(TAG, "Caller has a valid certificate, but its package doesn't match any ",
138             "expected package for the given certificate. Caller's package is ", callingPackage,
139             ". Expected packages as defined in res/xml/allowed_media_browser_callers.xml are (",
140             expectedPackages, "). This caller's certificate is: \n", signature);
141 
142         return false;
143     }
144 
145     private final static class CallerInfo {
146         final String name;
147         final String packageName;
148         final boolean release;
149         final String signingCertificate;
150 
CallerInfo(String name, String packageName, boolean release, String signingCertificate)151         public CallerInfo(String name, String packageName, boolean release,
152                           String signingCertificate) {
153             this.name = name;
154             this.packageName = packageName;
155             this.release = release;
156             this.signingCertificate = signingCertificate;
157         }
158     }
159 }
160