summaryrefslogtreecommitdiffstats
path: root/src/com
diff options
context:
space:
mode:
authorNick Pelly <npelly@google.com>2011-11-11 13:56:13 -0800
committerNick Pelly <npelly@google.com>2011-11-11 13:56:13 -0800
commit0d00a83fcea7bf96e118d0271ed807a3f39f0bc4 (patch)
tree62edf412a99d99f708a86b344e404b17bb443016 /src/com
parent1d9c95c9d31f71519d72b60e5af9cd64822ed87b (diff)
parentebf4b36e17dd0a63f293a618a6d1bcea98a69bd3 (diff)
downloadpackages_apps_nfc-0d00a83fcea7bf96e118d0271ed807a3f39f0bc4.zip
packages_apps_nfc-0d00a83fcea7bf96e118d0271ed807a3f39f0bc4.tar.gz
packages_apps_nfc-0d00a83fcea7bf96e118d0271ed807a3f39f0bc4.tar.bz2
resolved conflicts for merge of ebf4b36e to master
Change-Id: I73a3025a35da21db6600b6e82c99836641e9bee7
Diffstat (limited to 'src/com')
-rwxr-xr-xsrc/com/android/nfc/NfcService.java85
-rw-r--r--src/com/android/nfc/NfceeAccessControl.java289
2 files changed, 347 insertions, 27 deletions
diff --git a/src/com/android/nfc/NfcService.java b/src/com/android/nfc/NfcService.java
index 68e7598..4325183 100755
--- a/src/com/android/nfc/NfcService.java
+++ b/src/com/android/nfc/NfcService.java
@@ -35,7 +35,9 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
+import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.media.AudioManager;
import android.media.SoundPool;
import android.net.Uri;
@@ -71,22 +73,24 @@ import java.io.PrintWriter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.concurrent.ExecutionException;
public class NfcService extends Application implements DeviceHostListener {
private static final String ACTION_MASTER_CLEAR_NOTIFICATION = "android.intent.action.MASTER_CLEAR_NOTIFICATION";
- static final boolean DBG = true;
+ static final boolean DBG = false;
static final String TAG = "NfcService";
public static final String SERVICE_NAME = "nfc";
+ /** Regular NFC permission */
private static final String NFC_PERM = android.Manifest.permission.NFC;
private static final String NFC_PERM_ERROR = "NFC permission required";
+
+ /** NFC ADMIN permission - only for system apps */
private static final String ADMIN_PERM = android.Manifest.permission.WRITE_SECURE_SETTINGS;
private static final String ADMIN_PERM_ERROR = "WRITE_SECURE_SETTINGS permission required";
- private static final String NFCEE_ADMIN_PERM = "com.android.nfc.permission.NFCEE_ADMIN";
- private static final String NFCEE_ADMIN_PERM_ERROR = "NFCEE_ADMIN permission required";
public static final String PREF = "NfcServicePrefs";
@@ -194,6 +198,7 @@ public class NfcService extends Application implements DeviceHostListener {
NfcAdapterExtrasService mExtrasService;
boolean mIsAirplaneSensitive;
boolean mIsAirplaneToggleable;
+ NfceeAccessControl mNfceeAccessControl;
private NfcDispatcher mNfcDispatcher;
private KeyguardManager mKeyguard;
@@ -201,16 +206,18 @@ public class NfcService extends Application implements DeviceHostListener {
private static NfcService sService;
public static void enforceAdminPerm(Context context) {
- int admin = context.checkCallingOrSelfPermission(ADMIN_PERM);
- int nfcee = context.checkCallingOrSelfPermission(NFCEE_ADMIN_PERM);
- if (admin != PackageManager.PERMISSION_GRANTED
- && nfcee != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException(ADMIN_PERM_ERROR);
- }
+ context.enforceCallingOrSelfPermission(ADMIN_PERM, ADMIN_PERM_ERROR);
}
- public static void enforceNfceeAdminPerm(Context context) {
- context.enforceCallingOrSelfPermission(NFCEE_ADMIN_PERM, NFCEE_ADMIN_PERM_ERROR);
+ public void enforceNfceeAdminPerm(String pkg) {
+ if (pkg == null) {
+ throw new SecurityException("caller must pass a package name");
+ }
+ mContext.enforceCallingOrSelfPermission(NFC_PERM, NFC_PERM_ERROR);
+ if (!mNfceeAccessControl.check(Binder.getCallingUid(), pkg)) {
+ throw new SecurityException(NfceeAccessControl.NFCEE_ACCESS_PATH +
+ " denies NFCEE access to " + pkg);
+ }
}
public static NfcService getInstance() {
@@ -300,6 +307,8 @@ public class NfcService extends Application implements DeviceHostListener {
mSecureElement = new NativeNfcSecureElement();
mEeRoutingState = ROUTE_OFF;
+ mNfceeAccessControl = new NfceeAccessControl(this);
+
mPrefs = getSharedPreferences(PREF, Context.MODE_PRIVATE);
mPrefsEditor = mPrefs.edit();
@@ -725,8 +734,8 @@ public class NfcService extends Application implements DeviceHostListener {
}
@Override
- public INfcAdapterExtras getNfcAdapterExtrasInterface() {
- NfcService.enforceNfceeAdminPerm(mContext);
+ public INfcAdapterExtras getNfcAdapterExtrasInterface(String pkg) {
+ NfcService.this.enforceNfceeAdminPerm(pkg);
return mExtrasService;
}
@@ -1150,8 +1159,8 @@ public class NfcService extends Application implements DeviceHostListener {
}
@Override
- public Bundle open(IBinder b) throws RemoteException {
- NfcService.enforceNfceeAdminPerm(mContext);
+ public Bundle open(String pkg, IBinder b) throws RemoteException {
+ NfcService.this.enforceNfceeAdminPerm(pkg);
Bundle result;
try {
@@ -1194,8 +1203,10 @@ public class NfcService extends Application implements DeviceHostListener {
}
@Override
- public Bundle close() throws RemoteException {
- NfcService.enforceNfceeAdminPerm(mContext);
+ public Bundle close(String pkg, IBinder b) throws RemoteException {
+ NfcService.this.enforceNfceeAdminPerm(pkg);
+
+ b.unlinkToDeath(mOpenEe, 0);
Bundle result;
try {
@@ -1208,8 +1219,8 @@ public class NfcService extends Application implements DeviceHostListener {
}
@Override
- public Bundle transceive(byte[] in) throws RemoteException {
- NfcService.enforceNfceeAdminPerm(mContext);
+ public Bundle transceive(String pkg, byte[] in) throws RemoteException {
+ NfcService.this.enforceNfceeAdminPerm(pkg);
Bundle result;
byte[] out;
@@ -1240,21 +1251,21 @@ public class NfcService extends Application implements DeviceHostListener {
}
@Override
- public int getCardEmulationRoute() throws RemoteException {
- NfcService.enforceNfceeAdminPerm(mContext);
+ public int getCardEmulationRoute(String pkg) throws RemoteException {
+ NfcService.this.enforceNfceeAdminPerm(pkg);
return mEeRoutingState;
}
@Override
- public void setCardEmulationRoute(int route) throws RemoteException {
- NfcService.enforceNfceeAdminPerm(mContext);
+ public void setCardEmulationRoute(String pkg, int route) throws RemoteException {
+ NfcService.this.enforceNfceeAdminPerm(pkg);
mEeRoutingState = route;
applyRouting();
}
@Override
- public void authenticate(byte[] token) throws RemoteException {
- NfcService.enforceNfceeAdminPerm(mContext);
+ public void authenticate(String pkg, byte[] token) throws RemoteException {
+ NfcService.this.enforceNfceeAdminPerm(pkg);
}
};
@@ -1565,9 +1576,24 @@ public class NfcService extends Application implements DeviceHostListener {
}
private void sendSeBroadcast(Intent intent) {
- mNfcDispatcher.resumeAppSwitches();
+ PackageManager pm = getPackageManager();
intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
- mContext.sendBroadcast(intent, NFCEE_ADMIN_PERM);
+
+ // Resume app switches so the receivers can start activites without delay
+ mNfcDispatcher.resumeAppSwitches();
+
+ // Find the matching receivers and check each one for access permissions
+ List<ResolveInfo> receivers = pm.queryBroadcastReceivers(intent, 0);
+ for (ResolveInfo receiver : receivers) {
+ ActivityInfo activityInfo = receiver.activityInfo;
+ if (activityInfo != null) {
+ if (mNfceeAccessControl.check(activityInfo.applicationInfo)) {
+ intent.setComponent(new ComponentName(receiver.activityInfo.packageName,
+ receiver.activityInfo.name));
+ mContext.sendBroadcast(intent);
+ }
+ }
+ }
}
private boolean llcpActivated(NfcDepEndpoint device) {
@@ -1730,6 +1756,9 @@ public class NfcService extends Application implements DeviceHostListener {
Log.w(TAG, "failed to wipe NFC-EE");
}
} else if (action.equals(Intent.ACTION_PACKAGE_REMOVED)) {
+ // Clear the NFCEE access cache in case a UID gets recycled
+ mNfceeAccessControl.invalidateCache();
+
boolean dataRemoved = intent.getBooleanExtra(Intent.EXTRA_DATA_REMOVED, false);
if (dataRemoved) {
Uri data = intent.getData();
@@ -1800,7 +1829,9 @@ public class NfcService extends Application implements DeviceHostListener {
pw.println("mIsAirplaneSensitive=" + mIsAirplaneSensitive);
pw.println("mIsAirplaneToggleable=" + mIsAirplaneToggleable);
mP2pLinkManager.dump(fd, pw, args);
+ mNfceeAccessControl.dump(fd, pw, args);
pw.println(mDeviceHost.dump());
+
}
}
}
diff --git a/src/com/android/nfc/NfceeAccessControl.java b/src/com/android/nfc/NfceeAccessControl.java
new file mode 100644
index 0000000..9056b99
--- /dev/null
+++ b/src/com/android/nfc/NfceeAccessControl.java
@@ -0,0 +1,289 @@
+/*
+ * 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);
+ }
+ }
+ }
+}