/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.nfc; import java.io.File; import java.io.FileDescriptor; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserFactory; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.Signature; import android.content.pm.PackageManager.NameNotFoundException; import android.os.Environment; import android.util.Log; public class NfceeAccessControl { static final String TAG = "NfceeAccess"; static final boolean DBG = true; public static final String NFCEE_ACCESS_PATH = "/etc/nfcee_access.xml"; /** * Map of signatures to valid packages names, as read from nfcee_access.xml. * An empty list of package names indicates that any package * with this signature is allowed. */ final HashMap<Signature, String[]> mNfceeAccess; // contents final after onCreate() /** * Map from UID to NFCEE access, used as a cache. * Note: if a UID contains multiple packages they must all be * signed with the same certificate so in effect UID == certificate * used to sign the package. */ final HashMap<Integer, Boolean> mUidCache; // contents guarded by this final Context mContext; final boolean mDebugPrintSignature; NfceeAccessControl(Context context) { mContext = context; mNfceeAccess = new HashMap<Signature, String[]>(); mUidCache = new HashMap<Integer, Boolean>(); mDebugPrintSignature = parseNfceeAccess(); } /** * Check if the {uid, pkg} combination may use NFCEE. * Also verify with package manager that this {uid, pkg} combination * is valid if it is not cached. */ public boolean check(int uid, String pkg) { synchronized (this) { Boolean cached = mUidCache.get(uid); if (cached != null) { return cached; } boolean access = false; // Ensure the claimed package is present in the calling UID PackageManager pm = mContext.getPackageManager(); String[] pkgs = pm.getPackagesForUid(uid); for (String uidPkg : pkgs) { if (uidPkg.equals(pkg)) { // Ensure the package has access permissions if (checkPackageNfceeAccess(pkg)) { access = true; } break; } } mUidCache.put(uid, access); return access; } } /** * Check if the given ApplicationInfo may use the NFCEE. * Assumes ApplicationInfo came from package manager, * so no need to confirm {uid, pkg} is valid. */ public boolean check(ApplicationInfo info) { synchronized (this) { Boolean access = mUidCache.get(info.uid); if (access == null) { access = checkPackageNfceeAccess(info.packageName); mUidCache.put(info.uid, access); } return access; } } public void invalidateCache() { synchronized (this) { mUidCache.clear(); } } /** * Check with package manager if the pkg may use NFCEE. * Does not use cache. */ boolean checkPackageNfceeAccess(String pkg) { PackageManager pm = mContext.getPackageManager(); try { PackageInfo info = pm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES); if (info.signatures == null) { return false; } for (Signature s : info.signatures){ if (s == null) { continue; } String[] packages = mNfceeAccess.get(s); if (packages == null) { continue; } if (packages.length == 0) { // wildcard access if (DBG) Log.d(TAG, "Granted NFCEE access to " + pkg + " (wildcard)"); return true; } for (String p : packages) { if (pkg.equals(p)) { // explicit package access if (DBG) Log.d(TAG, "Granted access to " + pkg + " (explicit)"); return true; } } } if (mDebugPrintSignature) { Log.w(TAG, "denied NFCEE access for " + pkg + " with signature:"); for (Signature s : info.signatures) { if (s != null) { Log.w(TAG, s.toCharsString()); } } } } catch (NameNotFoundException e) { // ignore } return false; } /** * Parse nfcee_access.xml, populate mNfceeAccess * Policy is to ignore unexpected XML elements and continue processing, * except for obvious errors within a <signer> group since they might cause * package names to by ignored and therefore wildcard access granted * by mistake. Those errors invalidate the entire <signer> group. */ boolean parseNfceeAccess() { File file = new File(Environment.getRootDirectory(), NFCEE_ACCESS_PATH); FileReader reader = null; boolean debug = false; try { reader = new FileReader(file); XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); XmlPullParser parser = factory.newPullParser(); parser.setInput(reader); int event; ArrayList<String> packages = new ArrayList<String>(); Signature signature = null; parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false); while (true) { event = parser.next(); String tag = parser.getName(); if (event == XmlPullParser.START_TAG && "signer".equals(tag)) { signature = null; packages.clear(); for (int i = 0; i < parser.getAttributeCount(); i++) { if ("android:signature".equals(parser.getAttributeName(i))) { signature = new Signature(parser.getAttributeValue(i)); break; } } if (signature == null) { Log.w(TAG, "signer tag is missing android:signature attribute, igorning"); continue; } if (mNfceeAccess.containsKey(signature)) { Log.w(TAG, "duplicate signature, ignoring"); signature = null; continue; } } else if (event == XmlPullParser.END_TAG && "signer".equals(tag)) { if (signature == null) { Log.w(TAG, "mis-matched signer tag"); continue; } mNfceeAccess.put(signature, (String[])packages.toArray(new String[0])); packages.clear(); } else if (event == XmlPullParser.START_TAG && "package".equals(tag)) { if (signature == null) { Log.w(TAG, "ignoring unnested packge tag"); continue; } String name = null; for (int i = 0; i < parser.getAttributeCount(); i++) { if ("android:name".equals(parser.getAttributeName(i))) { name = parser.getAttributeValue(i); break; } } if (name == null) { Log.w(TAG, "package missing android:name, ignoring signer group"); signature = null; // invalidate signer continue; } // check for duplicate package names if (packages.contains(name)) { Log.w(TAG, "duplicate package name in signer group, ignoring"); continue; } packages.add(name); } else if (event == XmlPullParser.START_TAG && "debug".equals(tag)) { debug = true; } else if (event == XmlPullParser.END_DOCUMENT) { break; } } } catch (XmlPullParserException e) { Log.w(TAG, "failed to load NFCEE access list", e); mNfceeAccess.clear(); // invalidate entire access list } catch (FileNotFoundException e) { Log.w(TAG, "could not find " + NFCEE_ACCESS_PATH + ", no NFCEE access allowed"); } catch (IOException e) { Log.e(TAG, "Failed to load NFCEE access list", e); mNfceeAccess.clear(); // invalidate entire access list } finally { if (reader != null) { try { reader.close(); } catch (IOException e2) { } } } Log.i(TAG, "read " + mNfceeAccess.size() + " signature(s) for NFCEE access"); return debug; } public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("mNfceeAccess="); for (Signature s : mNfceeAccess.keySet()) { pw.printf("\t%s [", s.toCharsString()); String[] ps = mNfceeAccess.get(s); for (String p : ps) { pw.printf("%s, ", p); } pw.println("]"); } synchronized (this) { pw.println("mNfceeUidCache="); for (Integer uid : mUidCache.keySet()) { Boolean b = mUidCache.get(uid); pw.printf("\t%d %s\n", uid, b); } } } }