AccessRuleCache.java revision ab336de91e0468a1352d1a9f5d92f219140e0bd1
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
36package com.android.se.security;
37
38import android.os.Build;
39import android.util.Log;
40
41import com.android.se.security.gpac.AID_REF_DO;
42import com.android.se.security.gpac.AR_DO;
43import com.android.se.security.gpac.Hash_REF_DO;
44import com.android.se.security.gpac.REF_DO;
45
46import java.io.PrintWriter;
47import java.security.AccessControlException;
48import java.security.cert.Certificate;
49import java.security.cert.CertificateEncodingException;
50import java.util.ArrayList;
51import java.util.Arrays;
52import java.util.HashMap;
53import java.util.Iterator;
54import java.util.Map;
55import java.util.Set;
56
57/** Stores all the access rules from the Secure Element */
58public class AccessRuleCache {
59    private final boolean mDBG = 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
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
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. */
127    public void reset() {
128        mRefreshTag = null;
129        mRuleCache.clear();
130    }
131
132    /** Clears access rule cache only. */
133    public void clearCache() {
134        mRuleCache.clear();
135    }
136
137    /** Adds the Rule to the Cache */
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 */
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 (mDBG) {
240                Log.i(mTag, "Merged Access Rule: " + refDo.toString() + ", " + ca.toString());
241            }
242            return;
243        }
244        if (mDBG) {
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 */
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.ALLOWED);
277                    }
278                    if ((ca.getNFCEventAccess() == ChannelAccess.ACCESS.UNDEFINED)
279                            && (ca.getApduAccess() != ChannelAccess.ACCESS.UNDEFINED)) {
280                        ca.setNFCEventAccess(ca.getApduAccess());
281                    }
282                    if (mDBG) {
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 (mDBG) {
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 (mDBG) {
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.ALLOWED);
332                    }
333                    if ((ca.getNFCEventAccess() == ChannelAccess.ACCESS.UNDEFINED)
334                            && (ca.getApduAccess() != ChannelAccess.ACCESS.UNDEFINED)) {
335                        ca.setNFCEventAccess(ca.getApduAccess());
336                    }
337                    if (mDBG) {
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 (mDBG) {
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 (mDBG) {
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 (mDBG) 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     */
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        // C0 00 is specific -> default AID
403        // 4F 00 is NOT specific -> all AIDs
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     */
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 */
459    public boolean isRefreshTagEqual(byte[] refreshTag) {
460        if (refreshTag == null || mRefreshTag == null) return false;
461
462        return Arrays.equals(refreshTag, mRefreshTag);
463    }
464
465    public byte[] getRefreshTag() {
466        return mRefreshTag;
467    }
468
469    /** Sets the Refresh Tag */
470    public void setRefreshTag(byte[] refreshTag) {
471        mRefreshTag = refreshTag;
472    }
473
474    /** Debug information to be used by dumpsys */
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