Home | History | Annotate | Download | only in nfc
      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 = false;
     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, 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