• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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  * Copyright (c) 2015-2017, The Linux Foundation.
18  */
19 
20 /*
21  * Copyright 2012 Giesecke & Devrient GmbH.
22  *
23  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
24  * use this file except in compliance with the License. You may obtain a copy of
25  * the License at
26  *
27  * http://www.apache.org/licenses/LICENSE-2.0
28  *
29  * Unless required by applicable law or agreed to in writing, software
30  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
31  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
32  * License for the specific language governing permissions and limitations under
33  * the License.
34  */
35 
36 package com.android.se.security;
37 
38 import android.os.Build;
39 import android.util.Log;
40 
41 import com.android.se.security.gpac.AID_REF_DO;
42 import com.android.se.security.gpac.AR_DO;
43 import com.android.se.security.gpac.Hash_REF_DO;
44 import com.android.se.security.gpac.REF_DO;
45 
46 import java.io.PrintWriter;
47 import java.security.AccessControlException;
48 import java.security.cert.Certificate;
49 import java.security.cert.CertificateEncodingException;
50 import java.util.ArrayList;
51 import java.util.Arrays;
52 import java.util.HashMap;
53 import java.util.Iterator;
54 import java.util.Map;
55 import java.util.Set;
56 
57 /** Stores all the access rules from the Secure Element */
58 public class AccessRuleCache {
59     private static final boolean DEBUG = Build.IS_DEBUGGABLE;
60     private final String mTag = "SecureElement-AccessRuleCache";
61     // Previous "RefreshTag"
62     // 2012-09-25
63     // the refresh tag has to be valid as long as AxxController is valid
64     // a pure static element would cause that rules are not read any longer once the
65     // AxxController is
66     // recreated.
67     private byte[] mRefreshTag = null;
68     private Map<REF_DO, ChannelAccess> mRuleCache = new HashMap<REF_DO, ChannelAccess>();
69 
getAidRefDo(byte[] aid)70     private static AID_REF_DO getAidRefDo(byte[] aid) {
71         byte[] defaultAid = new byte[]{0x00, 0x00, 0x00, 0x00, 0x00};
72         if (aid == null || Arrays.equals(aid, defaultAid)) {
73             return new AID_REF_DO(AID_REF_DO.TAG_DEFAULT_APPLICATION);
74         } else {
75             return new AID_REF_DO(AID_REF_DO.TAG, aid);
76         }
77     }
78 
mapArDo2ChannelAccess(AR_DO arDo)79     private static ChannelAccess mapArDo2ChannelAccess(AR_DO arDo) {
80         ChannelAccess channelAccess = new ChannelAccess();
81 
82         // check apdu access allowance
83         if (arDo.getApduArDo() != null) {
84             // first if there is a rule for access, reset the general deny flag.
85             channelAccess.setAccess(ChannelAccess.ACCESS.ALLOWED, "");
86             channelAccess.setUseApduFilter(false);
87 
88             if (arDo.getApduArDo().isApduAllowed()) {
89                 // check the apdu filter
90                 ArrayList<byte[]> apduHeaders = arDo.getApduArDo().getApduHeaderList();
91                 ArrayList<byte[]> filterMasks = arDo.getApduArDo().getFilterMaskList();
92                 if (apduHeaders != null && filterMasks != null && apduHeaders.size() > 0
93                         && apduHeaders.size() == filterMasks.size()) {
94                     ApduFilter[] accessConditions = new ApduFilter[apduHeaders.size()];
95                     for (int i = 0; i < apduHeaders.size(); i++) {
96                         accessConditions[i] = new ApduFilter(apduHeaders.get(i),
97                                 filterMasks.get(i));
98                     }
99                     channelAccess.setUseApduFilter(true);
100                     channelAccess.setApduFilter(accessConditions);
101                 } else {
102                     // general APDU access
103                     channelAccess.setApduAccess(ChannelAccess.ACCESS.ALLOWED);
104                 }
105             } else {
106                 // apdu access is not allowed at all.
107                 channelAccess.setApduAccess(ChannelAccess.ACCESS.DENIED);
108             }
109         } else {
110             channelAccess.setAccess(ChannelAccess.ACCESS.DENIED, "No APDU access rule available.!");
111         }
112 
113         // check for NFC Event allowance
114         if (arDo.getNfcArDo() != null) {
115             channelAccess.setNFCEventAccess(
116                     arDo.getNfcArDo().isNfcAllowed()
117                             ? ChannelAccess.ACCESS.ALLOWED
118                             : ChannelAccess.ACCESS.DENIED);
119         } else {
120             // GP says that by default NFC should have the same right as for APDU access
121             channelAccess.setNFCEventAccess(channelAccess.getApduAccess());
122         }
123         return channelAccess;
124     }
125 
126     /** Clears access rule cache and refresh tag. */
reset()127     public void reset() {
128         mRefreshTag = null;
129         mRuleCache.clear();
130     }
131 
132     /** Clears access rule cache only. */
clearCache()133     public void clearCache() {
134         mRuleCache.clear();
135     }
136 
137     /** Adds the Rule to the Cache */
putWithMerge(REF_DO refDo, AR_DO arDo)138     public void putWithMerge(REF_DO refDo, AR_DO arDo) {
139         ChannelAccess channelAccess = mapArDo2ChannelAccess(arDo);
140         putWithMerge(refDo, channelAccess);
141     }
142 
143     /** Adds the Rule to the Cache */
putWithMerge(REF_DO refDo, ChannelAccess channelAccess)144     public void putWithMerge(REF_DO refDo, ChannelAccess channelAccess) {
145         if (mRuleCache.containsKey(refDo)) {
146             ChannelAccess ca = mRuleCache.get(refDo);
147 
148             // if new ac condition is more restrictive then use their settings
149 
150             if ((channelAccess.getAccess() == ChannelAccess.ACCESS.DENIED)
151                     || (ca.getAccess() == ChannelAccess.ACCESS.DENIED)) {
152                 ca.setAccess(ChannelAccess.ACCESS.DENIED, channelAccess.getReason());
153             } else if ((channelAccess.getAccess() == ChannelAccess.ACCESS.UNDEFINED)
154                     && (ca.getAccess() != ChannelAccess.ACCESS.UNDEFINED)) {
155                 ca.setAccess(ca.getAccess(), ca.getReason());
156             } else if ((channelAccess.getAccess() != ChannelAccess.ACCESS.UNDEFINED)
157                     && (ca.getAccess() == ChannelAccess.ACCESS.UNDEFINED)) {
158                 ca.setAccess(channelAccess.getAccess(), channelAccess.getReason());
159             } else {
160                 ca.setAccess(ChannelAccess.ACCESS.ALLOWED, ca.getReason());
161             }
162 
163             // if new rule says NFC is denied then use it
164             // if current rule as undefined NFC rule then use setting of new rule.
165             // current NFC    new NFC     resulting NFC
166             // UNDEFINED      x           x
167             // ALLOWED        !DENIED     ALLOWED
168             // ALLOWED        DENIED      DENIED
169             // DENIED         !DENIED     DENIED
170             // DENIED         DENIED      DENIED
171 
172             if ((channelAccess.getNFCEventAccess() == ChannelAccess.ACCESS.DENIED)
173                     || (ca.getNFCEventAccess() == ChannelAccess.ACCESS.DENIED)) {
174                 ca.setNFCEventAccess(ChannelAccess.ACCESS.DENIED);
175             } else if ((channelAccess.getNFCEventAccess() == ChannelAccess.ACCESS.UNDEFINED)
176                     && (ca.getNFCEventAccess() != ChannelAccess.ACCESS.UNDEFINED)) {
177                 ca.setNFCEventAccess(ca.getNFCEventAccess());
178             } else if ((channelAccess.getNFCEventAccess() != ChannelAccess.ACCESS.UNDEFINED)
179                     && (ca.getNFCEventAccess() == ChannelAccess.ACCESS.UNDEFINED)) {
180                 ca.setNFCEventAccess(channelAccess.getNFCEventAccess());
181             } else {
182                 ca.setNFCEventAccess(ChannelAccess.ACCESS.ALLOWED);
183             }
184             // if new rule says APUD is denied then use it
185             // if current rule as undefined APDU rule then use setting of new rule.
186             // current APDU  new APDU    resulting APDU
187             // UNDEFINED     x           x
188             // ALLOWED       !DENIED     ALLOWED
189             // ALLOWED       DENIED      DENIED
190             // DENIED        !DENIED     DENIED
191             // DENEID        DENIED      DENIED
192 
193             if ((channelAccess.getApduAccess() == ChannelAccess.ACCESS.DENIED)
194                     || (ca.getApduAccess() == ChannelAccess.ACCESS.DENIED)) {
195                 ca.setApduAccess(ChannelAccess.ACCESS.DENIED);
196             } else if ((channelAccess.getApduAccess() == ChannelAccess.ACCESS.UNDEFINED)
197                     && (ca.getApduAccess() != ChannelAccess.ACCESS.UNDEFINED)) {
198                 ca.setApduAccess(ca.getApduAccess());
199             } else if ((channelAccess.getApduAccess() == ChannelAccess.ACCESS.UNDEFINED)
200                     && (ca.getApduAccess() == ChannelAccess.ACCESS.UNDEFINED)
201                     && !channelAccess.isUseApduFilter()) {
202                 ca.setApduAccess(ChannelAccess.ACCESS.DENIED);
203             } else if ((channelAccess.getApduAccess() != ChannelAccess.ACCESS.UNDEFINED)
204                     && (ca.getApduAccess() == ChannelAccess.ACCESS.UNDEFINED)) {
205                 ca.setApduAccess(channelAccess.getApduAccess());
206             } else {
207                 ca.setApduAccess(ChannelAccess.ACCESS.ALLOWED);
208             }
209 
210             // put APDU filter together if resulting APDU access is allowed.
211             if ((ca.getApduAccess() == ChannelAccess.ACCESS.ALLOWED)
212                     || (ca.getApduAccess() == ChannelAccess.ACCESS.UNDEFINED)) {
213                 Log.i(mTag, "Merged Access Rule:  APDU filter together");
214                 if (channelAccess.isUseApduFilter()) {
215                     ca.setUseApduFilter(true);
216                     ApduFilter[] filter = ca.getApduFilter();
217                     ApduFilter[] filter2 = channelAccess.getApduFilter();
218                     if (filter == null || filter.length == 0) {
219                         ca.setApduFilter(filter2);
220                     } else if (filter2 == null || filter2.length == 0) {
221                         ca.setApduFilter(filter);
222                     } else {
223                         ApduFilter[] sum = new ApduFilter[filter.length + filter2.length];
224                         int i = 0;
225                         for (ApduFilter f : filter) {
226                             sum[i++] = f;
227                         }
228                         for (ApduFilter f : filter2) {
229                             sum[i++] = f;
230                         }
231                         ca.setApduFilter(sum);
232                     }
233                 }
234             } else {
235                 // if APDU access is not allowed the remove also all apdu filter.
236                 ca.setUseApduFilter(false);
237                 ca.setApduFilter(null);
238             }
239             if (DEBUG) {
240                 Log.i(mTag, "Merged Access Rule: " + refDo.toString() + ", " + ca.toString());
241             }
242             return;
243         }
244         if (DEBUG) {
245             Log.i(mTag, "Add Access Rule: " + refDo.toString() + ", " + channelAccess.toString());
246         }
247         mRuleCache.put(refDo, channelAccess);
248     }
249 
250     /** Find Access Rule for the given AID and Application */
findAccessRule(byte[] aid, Certificate[] appCerts)251     public ChannelAccess findAccessRule(byte[] aid, Certificate[] appCerts)
252             throws AccessControlException {
253 
254         // TODO: check difference between DeviceCertHash and Certificate Chain (EndEntityCertHash,
255         // IntermediateCertHash (1..n), RootCertHash)
256         // The DeviceCertificate is equal to the EndEntityCertificate.
257         // The android systems seems always to deliver only the EndEntityCertificate, but this
258         // seems not
259         // to be sure.
260         // thats why we implement the whole chain.
261 
262 
263         /* Search Rule A ( Certificate(s); AID ) */
264         AID_REF_DO aid_ref_do = getAidRefDo(aid);
265         REF_DO ref_do;
266         Hash_REF_DO hash_ref_do;
267         for (Certificate appCert : appCerts) {
268             try {
269                 hash_ref_do = new Hash_REF_DO(AccessControlEnforcer.getAppCertHash(appCert));
270                 ref_do = new REF_DO(aid_ref_do, hash_ref_do);
271 
272                 if (mRuleCache.containsKey(ref_do)) {
273                     // let's take care about the undefined rules, according to the GP specification:
274                     ChannelAccess ca = mRuleCache.get(ref_do);
275                     if (ca.getApduAccess() == ChannelAccess.ACCESS.UNDEFINED) {
276                         ca.setApduAccess(ChannelAccess.ACCESS.DENIED);
277                     }
278                     if ((ca.getNFCEventAccess() == ChannelAccess.ACCESS.UNDEFINED)
279                             && (ca.getApduAccess() != ChannelAccess.ACCESS.UNDEFINED)) {
280                         ca.setNFCEventAccess(ca.getApduAccess());
281                     }
282                     if (DEBUG) {
283                         Log.i(mTag, "findAccessRule() " + ref_do.toString() + ", "
284                                 + mRuleCache.get(ref_do).toString());
285                     }
286                     return mRuleCache.get(ref_do);
287                 }
288             } catch (CertificateEncodingException e) {
289                 throw new AccessControlException("Problem with Application Certificate.");
290             }
291         }
292         // no rule found,
293         // now we have to check if the given AID
294         // is used together with another specific hash value (another device application)
295         if (searchForRulesWithSpecificAidButOtherHash(aid_ref_do) != null) {
296             if (DEBUG) {
297                 Log.i(mTag, "Conflict Resolution Case A returning access rule \'NEVER\'.");
298             }
299             ChannelAccess ca = new ChannelAccess();
300             ca.setApduAccess(ChannelAccess.ACCESS.DENIED);
301             ca.setAccess(ChannelAccess.ACCESS.DENIED,
302                     "AID has a specific access rule with a different hash. (Case A)");
303             ca.setNFCEventAccess(ChannelAccess.ACCESS.DENIED);
304             return ca;
305         }
306 
307         // SearchRule B ( <AllDeviceApplications>; AID)
308         aid_ref_do = getAidRefDo(aid);
309         hash_ref_do = new Hash_REF_DO(); // empty hash ref
310         ref_do = new REF_DO(aid_ref_do, hash_ref_do);
311 
312         if (mRuleCache.containsKey(ref_do)) {
313             if (DEBUG) {
314                 Log.i(mTag, "findAccessRule() " + ref_do.toString() + ", "
315                         + mRuleCache.get(ref_do).toString());
316             }
317             return mRuleCache.get(ref_do);
318         }
319 
320         // Search Rule C ( Certificate(s); <AllSEApplications> )
321         aid_ref_do = new AID_REF_DO(AID_REF_DO.TAG);
322         for (Certificate appCert : appCerts) {
323             try {
324                 hash_ref_do = new Hash_REF_DO(AccessControlEnforcer.getAppCertHash(appCert));
325                 ref_do = new REF_DO(aid_ref_do, hash_ref_do);
326 
327                 if (mRuleCache.containsKey(ref_do)) {
328                     // let's take care about the undefined rules, according to the GP specification:
329                     ChannelAccess ca = mRuleCache.get(ref_do);
330                     if (ca.getApduAccess() == ChannelAccess.ACCESS.UNDEFINED) {
331                         ca.setApduAccess(ChannelAccess.ACCESS.DENIED);
332                     }
333                     if ((ca.getNFCEventAccess() == ChannelAccess.ACCESS.UNDEFINED)
334                             && (ca.getApduAccess() != ChannelAccess.ACCESS.UNDEFINED)) {
335                         ca.setNFCEventAccess(ca.getApduAccess());
336                     }
337                     if (DEBUG) {
338                         Log.i(mTag, "findAccessRule() " + ref_do.toString() + ", "
339                                 + mRuleCache.get(ref_do).toString());
340                     }
341                     return mRuleCache.get(ref_do);
342                 }
343             } catch (CertificateEncodingException e) {
344                 throw new AccessControlException("Problem with Application Certificate.");
345             }
346         }
347 
348         // no rule found,
349         // now we have to check if the all AID DO
350         // is used together with another Hash
351         if (searchForRulesWithAllAidButOtherHash() != null) {
352             if (DEBUG) {
353                 Log.i(mTag, "Conflict Resolution Case C returning access rule \'NEVER\'.");
354             }
355             ChannelAccess ca = new ChannelAccess();
356             ca.setApduAccess(ChannelAccess.ACCESS.DENIED);
357             ca.setAccess(
358                     ChannelAccess.ACCESS.DENIED,
359                     "An access rule with a different hash and all AIDs was found. (Case C)");
360             ca.setNFCEventAccess(ChannelAccess.ACCESS.DENIED);
361             return ca;
362         }
363 
364         // SearchRule D ( <AllDeviceApplications>; <AllSEApplications>)
365         aid_ref_do = new AID_REF_DO(AID_REF_DO.TAG);
366         hash_ref_do = new Hash_REF_DO();
367         ref_do = new REF_DO(aid_ref_do, hash_ref_do);
368 
369         if (mRuleCache.containsKey(ref_do)) {
370             if (DEBUG) {
371                 Log.i(mTag, "findAccessRule() " + ref_do.toString() + ", "
372                         + mRuleCache.get(ref_do).toString());
373             }
374             return mRuleCache.get(ref_do);
375         }
376 
377         if (DEBUG) Log.i(mTag, "findAccessRule() not found");
378         return null;
379     }
380 
381     /*
382      * The GP_SE_AC spec says:
383      * According to the rule conflict resolution process defined in section 3.2.1, if a specific
384      * rule exists
385      * that associates another device application with the SE application identified by AID (e.g.
386       * there is
387      * a rule associating AID with the hash of another device application), then the ARA-M (when
388      * using GET DATA [Specific]) or the Access Control Enforcer (when using GET DATA [All]) shall
389      * set the result of SearchRuleFor(DeviceApplicationCertificate, AID) to NEVER (i.e. precedence
390      * of specific rules over generic rules)
391      *
392      * In own words:
393      * Search the rules cache for a rule that contains the wanted AID but with another specific
394      * Hash value.
395      */
searchForRulesWithSpecificAidButOtherHash(AID_REF_DO aidRefDo)396     private REF_DO searchForRulesWithSpecificAidButOtherHash(AID_REF_DO aidRefDo) {
397 
398         // AID has to be specific
399         if (aidRefDo == null) {
400             return null;
401         }
402 
403         // The specified AID_REF_DO does not have any AID and it is not for the default AID.
404         if (aidRefDo.getTag() == AID_REF_DO.TAG && aidRefDo.getAid().length == 0) {
405             return null;
406         }
407 
408         Set<REF_DO> keySet = mRuleCache.keySet();
409         Iterator<REF_DO> iter = keySet.iterator();
410         while (iter.hasNext()) {
411             REF_DO ref_do = iter.next();
412             if (aidRefDo.equals(ref_do.getAidDo())) {
413                 if (ref_do.getHashDo() != null
414                         && ref_do.getHashDo().getHash().length > 0) {
415                     // this ref_do contains the search AID and a specific hash value
416                     return ref_do;
417                 }
418             }
419         }
420         return null;
421     }
422 
423     /*
424      * The GP_SE_AC spec says:
425      * According to the rule conflict resolution process defined in section 3.2.1, if a specific
426      * rule exists
427      * that associates another device application with the SE application identified by AID (e.g.
428       * there is
429      * a rule associating AID with the hash of another device application), then the ARA-M (when
430      * using GET DATA [Specific]) or the Access Control Enforcer (when using GET DATA [All]) shall
431      * set the result of SearchRuleFor(DeviceApplicationCertificate, AID) to NEVER (i.e. precedence
432      * of specific rules over generic rules)
433      *
434      * In own words:
435      * Search the rules cache for a rule that contains a Hash with an all SE AID (4F 00).
436      */
searchForRulesWithAllAidButOtherHash()437     private Object searchForRulesWithAllAidButOtherHash() {
438 
439         AID_REF_DO aid_ref_do = new AID_REF_DO(AID_REF_DO.TAG);
440 
441         Set<REF_DO> keySet = mRuleCache.keySet();
442         Iterator<REF_DO> iter = keySet.iterator();
443         while (iter.hasNext()) {
444             REF_DO ref_do = iter.next();
445             if (aid_ref_do.equals(ref_do.getAidDo())) {
446                 // aid tlv is equal
447                 if (ref_do.getHashDo() != null
448                         && ref_do.getHashDo().getHash().length > 0) {
449                     // return ref_do if
450                     // a HASH value is available and has a length > 0 (SHA1_LEN)
451                     return ref_do;
452                 }
453             }
454         }
455         return null;
456     }
457 
458     /** Check if the given Refresh Tag is equal to the last known */
isRefreshTagEqual(byte[] refreshTag)459     public boolean isRefreshTagEqual(byte[] refreshTag) {
460         if (refreshTag == null || mRefreshTag == null) return false;
461 
462         return Arrays.equals(refreshTag, mRefreshTag);
463     }
464 
getRefreshTag()465     public byte[] getRefreshTag() {
466         return mRefreshTag;
467     }
468 
469     /** Sets the Refresh Tag */
setRefreshTag(byte[] refreshTag)470     public void setRefreshTag(byte[] refreshTag) {
471         mRefreshTag = refreshTag;
472     }
473 
474     /** Debug information to be used by dumpsys */
dump(PrintWriter writer)475     public void dump(PrintWriter writer) {
476         writer.println(mTag + ":");
477 
478         /* Dump the refresh tag */
479         writer.print("Current refresh tag is: ");
480         if (mRefreshTag == null) {
481             writer.print("<null>");
482         } else {
483             for (byte oneByte : mRefreshTag) writer.printf("%02X:", oneByte);
484         }
485         writer.println();
486 
487         /* Dump the rules cache */
488         writer.println("Rules:");
489         int i = 0;
490         for (Map.Entry<REF_DO, ChannelAccess> entry : mRuleCache.entrySet()) {
491             i++;
492             writer.print("rule " + i + ": ");
493             writer.println(entry.getKey().toString() + " -> " + entry.getValue().toString());
494         }
495         writer.println();
496     }
497 }
498