Home | History | Annotate | Download | only in mediabrowserservice
      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 
     50     public PackageValidator(Context ctx) {
     51         mValidCertificates = readValidCertificates(ctx.getResources().getXml(
     52             R.xml.allowed_media_browser_callers));
     53     }
     54 
     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")
     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 
    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