| /* |
| * 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, 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); |
| } |
| } |
| } |
| } |