1 /* 2 * Copyright (C) 2011 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.android.nfc; 18 19 import java.io.File; 20 import java.io.FileDescriptor; 21 import java.io.FileNotFoundException; 22 import java.io.FileReader; 23 import java.io.IOException; 24 import java.io.PrintWriter; 25 import java.util.ArrayList; 26 import java.util.HashMap; 27 28 import org.xmlpull.v1.XmlPullParser; 29 import org.xmlpull.v1.XmlPullParserException; 30 import org.xmlpull.v1.XmlPullParserFactory; 31 32 import android.content.Context; 33 import android.content.pm.ApplicationInfo; 34 import android.content.pm.PackageInfo; 35 import android.content.pm.PackageManager; 36 import android.content.pm.Signature; 37 import android.content.pm.PackageManager.NameNotFoundException; 38 import android.os.Environment; 39 import android.util.Log; 40 41 public class NfceeAccessControl { 42 static final String TAG = "NfceeAccess"; 43 static final boolean DBG = true; 44 45 public static final String NFCEE_ACCESS_PATH = "/etc/nfcee_access.xml"; 46 47 /** 48 * Map of signatures to valid packages names, as read from nfcee_access.xml. 49 * An empty list of package names indicates that any package 50 * with this signature is allowed. 51 */ 52 final HashMap<Signature, String[]> mNfceeAccess; // contents final after onCreate() 53 54 /** 55 * Map from UID to NFCEE access, used as a cache. 56 * Note: if a UID contains multiple packages they must all be 57 * signed with the same certificate so in effect UID == certificate 58 * used to sign the package. 59 */ 60 final HashMap<Integer, Boolean> mUidCache; // contents guarded by this 61 62 final Context mContext; 63 final boolean mDebugPrintSignature; 64 65 NfceeAccessControl(Context context) { 66 mContext = context; 67 mNfceeAccess = new HashMap<Signature, String[]>(); 68 mUidCache = new HashMap<Integer, Boolean>(); 69 mDebugPrintSignature = parseNfceeAccess(); 70 } 71 72 /** 73 * Check if the {uid, pkg} combination may use NFCEE. 74 * Also verify with package manager that this {uid, pkg} combination 75 * is valid if it is not cached. 76 */ 77 public boolean check(int uid, String pkg) { 78 synchronized (this) { 79 Boolean cached = mUidCache.get(uid); 80 if (cached != null) { 81 return cached; 82 } 83 84 boolean access = false; 85 86 // Ensure the claimed package is present in the calling UID 87 PackageManager pm = mContext.getPackageManager(); 88 String[] pkgs = pm.getPackagesForUid(uid); 89 for (String uidPkg : pkgs) { 90 if (uidPkg.equals(pkg)) { 91 // Ensure the package has access permissions 92 if (checkPackageNfceeAccess(pkg)) { 93 access = true; 94 } 95 break; 96 } 97 } 98 99 mUidCache.put(uid, access); 100 return access; 101 } 102 } 103 104 /** 105 * Check if the given ApplicationInfo may use the NFCEE. 106 * Assumes ApplicationInfo came from package manager, 107 * so no need to confirm {uid, pkg} is valid. 108 */ 109 public boolean check(ApplicationInfo info) { 110 synchronized (this) { 111 Boolean access = mUidCache.get(info.uid); 112 if (access == null) { 113 access = checkPackageNfceeAccess(info.packageName); 114 mUidCache.put(info.uid, access); 115 } 116 return access; 117 } 118 } 119 120 public void invalidateCache() { 121 synchronized (this) { 122 mUidCache.clear(); 123 } 124 } 125 126 /** 127 * Check with package manager if the pkg may use NFCEE. 128 * Does not use cache. 129 */ 130 boolean checkPackageNfceeAccess(String pkg) { 131 PackageManager pm = mContext.getPackageManager(); 132 try { 133 PackageInfo info = pm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES); 134 if (info.signatures == null) { 135 return false; 136 } 137 138 for (Signature s : info.signatures){ 139 if (s == null) { 140 continue; 141 } 142 String[] packages = mNfceeAccess.get(s); 143 if (packages == null) { 144 continue; 145 } 146 if (packages.length == 0) { 147 // wildcard access 148 if (DBG) Log.d(TAG, "Granted NFCEE access to " + pkg + " (wildcard)"); 149 return true; 150 } 151 for (String p : packages) { 152 if (pkg.equals(p)) { 153 // explicit package access 154 if (DBG) Log.d(TAG, "Granted access to " + pkg + " (explicit)"); 155 return true; 156 } 157 } 158 } 159 160 if (mDebugPrintSignature) { 161 Log.w(TAG, "denied NFCEE access for " + pkg + " with signature:"); 162 for (Signature s : info.signatures) { 163 if (s != null) { 164 Log.w(TAG, s.toCharsString()); 165 } 166 } 167 } 168 } catch (NameNotFoundException e) { 169 // ignore 170 } 171 return false; 172 } 173 174 /** 175 * Parse nfcee_access.xml, populate mNfceeAccess 176 * Policy is to ignore unexpected XML elements and continue processing, 177 * except for obvious errors within a <signer> group since they might cause 178 * package names to by ignored and therefore wildcard access granted 179 * by mistake. Those errors invalidate the entire <signer> group. 180 */ 181 boolean parseNfceeAccess() { 182 File file = new File(Environment.getRootDirectory(), NFCEE_ACCESS_PATH); 183 FileReader reader = null; 184 boolean debug = false; 185 try { 186 reader = new FileReader(file); 187 XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); 188 XmlPullParser parser = factory.newPullParser(); 189 parser.setInput(reader); 190 191 int event; 192 ArrayList<String> packages = new ArrayList<String>(); 193 Signature signature = null; 194 parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false); 195 while (true) { 196 event = parser.next(); 197 String tag = parser.getName(); 198 if (event == XmlPullParser.START_TAG && "signer".equals(tag)) { 199 signature = null; 200 packages.clear(); 201 for (int i = 0; i < parser.getAttributeCount(); i++) { 202 if ("android:signature".equals(parser.getAttributeName(i))) { 203 signature = new Signature(parser.getAttributeValue(i)); 204 break; 205 } 206 } 207 if (signature == null) { 208 Log.w(TAG, "signer tag is missing android:signature attribute, igorning"); 209 continue; 210 } 211 if (mNfceeAccess.containsKey(signature)) { 212 Log.w(TAG, "duplicate signature, ignoring"); 213 signature = null; 214 continue; 215 } 216 } else if (event == XmlPullParser.END_TAG && "signer".equals(tag)) { 217 if (signature == null) { 218 Log.w(TAG, "mis-matched signer tag"); 219 continue; 220 } 221 mNfceeAccess.put(signature, (String[])packages.toArray(new String[0])); 222 packages.clear(); 223 } else if (event == XmlPullParser.START_TAG && "package".equals(tag)) { 224 if (signature == null) { 225 Log.w(TAG, "ignoring unnested packge tag"); 226 continue; 227 } 228 String name = null; 229 for (int i = 0; i < parser.getAttributeCount(); i++) { 230 if ("android:name".equals(parser.getAttributeName(i))) { 231 name = parser.getAttributeValue(i); 232 break; 233 } 234 } 235 if (name == null) { 236 Log.w(TAG, "package missing android:name, ignoring signer group"); 237 signature = null; // invalidate signer 238 continue; 239 } 240 // check for duplicate package names 241 if (packages.contains(name)) { 242 Log.w(TAG, "duplicate package name in signer group, ignoring"); 243 continue; 244 } 245 packages.add(name); 246 } else if (event == XmlPullParser.START_TAG && "debug".equals(tag)) { 247 debug = true; 248 } else if (event == XmlPullParser.END_DOCUMENT) { 249 break; 250 } 251 } 252 } catch (XmlPullParserException e) { 253 Log.w(TAG, "failed to load NFCEE access list", e); 254 mNfceeAccess.clear(); // invalidate entire access list 255 } catch (FileNotFoundException e) { 256 Log.w(TAG, "could not find " + NFCEE_ACCESS_PATH + ", no NFCEE access allowed"); 257 } catch (IOException e) { 258 Log.e(TAG, "Failed to load NFCEE access list", e); 259 mNfceeAccess.clear(); // invalidate entire access list 260 } finally { 261 if (reader != null) { 262 try { 263 reader.close(); 264 } catch (IOException e2) { } 265 } 266 } 267 Log.i(TAG, "read " + mNfceeAccess.size() + " signature(s) for NFCEE access"); 268 return debug; 269 } 270 271 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 272 pw.println("mNfceeAccess="); 273 for (Signature s : mNfceeAccess.keySet()) { 274 pw.printf("\t%s [", s.toCharsString()); 275 String[] ps = mNfceeAccess.get(s); 276 for (String p : ps) { 277 pw.printf("%s, ", p); 278 } 279 pw.println("]"); 280 } 281 synchronized (this) { 282 pw.println("mNfceeUidCache="); 283 for (Integer uid : mUidCache.keySet()) { 284 Boolean b = mUidCache.get(uid); 285 pw.printf("\t%d %s\n", uid, b); 286 } 287 } 288 } 289 } 290