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