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 PackageValidator(Context ctx)52 public PackageValidator(Context ctx) { 53 mValidCertificates = readValidCertificates(ctx.getResources().getXml( 54 R.xml.allowed_media_browser_callers)); 55 } 56 readValidCertificates(XmlResourceParser parser)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") isCallerAllowed(Context context, String callingPackage, int callingUid)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 CallerInfo(String name, String packageName, boolean release, String signingCertificate)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