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