• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
NfceeAccessControl(Context context)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      */
check(int uid, String pkg)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      */
check(ApplicationInfo info)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 
invalidateCache()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      */
checkPackageNfceeAccess(String pkg)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      */
parseNfceeAccess()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 
dump(FileDescriptor fd, PrintWriter pw, String[] args)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