| /* |
| * Copyright (C) 2017 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. |
| */ |
| /* |
| * Copyright (c) 2015-2017, The Linux Foundation. |
| */ |
| |
| /* |
| * Copyright 2012 Giesecke & Devrient GmbH. |
| * |
| * 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.se.security; |
| |
| import android.os.Build; |
| import android.util.Log; |
| |
| import com.android.se.security.gpac.AID_REF_DO; |
| import com.android.se.security.gpac.AR_DO; |
| import com.android.se.security.gpac.Hash_REF_DO; |
| import com.android.se.security.gpac.PKG_REF_DO; |
| import com.android.se.security.gpac.REF_DO; |
| |
| import java.io.PrintWriter; |
| import java.security.AccessControlException; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** Stores all the access rules from the Secure Element */ |
| public class AccessRuleCache { |
| private static final boolean DEBUG = Build.IS_DEBUGGABLE; |
| private final String mTag = "SecureElement-AccessRuleCache"; |
| // Previous "RefreshTag" |
| // 2012-09-25 |
| // the refresh tag has to be valid as long as AxxController is valid |
| // a pure static element would cause that rules are not read any longer once the |
| // AxxController is |
| // recreated. |
| private byte[] mRefreshTag = null; |
| private Map<REF_DO, ChannelAccess> mRuleCache = new HashMap<REF_DO, ChannelAccess>(); |
| private ArrayList<REF_DO> mCarrierPrivilegeCache = new ArrayList<REF_DO>(); |
| |
| private static AID_REF_DO getAidRefDo(byte[] aid) { |
| byte[] defaultAid = new byte[]{0x00, 0x00, 0x00, 0x00, 0x00}; |
| if (aid == null || Arrays.equals(aid, defaultAid)) { |
| return new AID_REF_DO(AID_REF_DO.TAG_DEFAULT_APPLICATION); |
| } else { |
| return new AID_REF_DO(AID_REF_DO.TAG, aid); |
| } |
| } |
| |
| private static ChannelAccess mapArDo2ChannelAccess(AR_DO arDo) { |
| ChannelAccess channelAccess = new ChannelAccess(); |
| |
| // Missing access rule attribute shall be interpreted as ALWAYS or NEVER |
| // after the result of the rule conflict resolution and combination is processed. |
| // See Table G-1 in GP SEAC v1.1 Annex G. |
| // |
| // GP SEAC v1.0 also indicates the same rule in Annex D. |
| // Combined rule of APDU (ALWAYS) and NFC (ALWAYS) shall be APDU (ALWAYS) + NFC (ALWAYS). |
| |
| // check apdu access allowance |
| if (arDo.getApduArDo() != null) { |
| if (arDo.getApduArDo().isApduAllowed()) { |
| channelAccess.setAccess(ChannelAccess.ACCESS.ALLOWED, ""); |
| // check the apdu filter |
| ArrayList<byte[]> apduHeaders = arDo.getApduArDo().getApduHeaderList(); |
| ArrayList<byte[]> filterMasks = arDo.getApduArDo().getFilterMaskList(); |
| if (apduHeaders != null && filterMasks != null && apduHeaders.size() > 0 |
| && apduHeaders.size() == filterMasks.size()) { |
| ApduFilter[] accessConditions = new ApduFilter[apduHeaders.size()]; |
| for (int i = 0; i < apduHeaders.size(); i++) { |
| accessConditions[i] = new ApduFilter(apduHeaders.get(i), |
| filterMasks.get(i)); |
| } |
| channelAccess.setUseApduFilter(true); |
| channelAccess.setApduFilter(accessConditions); |
| } else { |
| // general APDU access |
| channelAccess.setApduAccess(ChannelAccess.ACCESS.ALLOWED); |
| } |
| } else { |
| // apdu access is not allowed at all. |
| channelAccess.setAccess(ChannelAccess.ACCESS.DENIED, |
| "NEVER is explicitly specified as the APDU access rule policy"); |
| channelAccess.setApduAccess(ChannelAccess.ACCESS.DENIED); |
| } |
| } else { |
| // It is too early to interpret the missing APDU access rule attribute as NEVER. |
| } |
| |
| // check for NFC Event allowance |
| if (arDo.getNfcArDo() != null) { |
| channelAccess.setNFCEventAccess( |
| arDo.getNfcArDo().isNfcAllowed() |
| ? ChannelAccess.ACCESS.ALLOWED |
| : ChannelAccess.ACCESS.DENIED); |
| } else { |
| // It is too early to interpret the missing NFC access rule attribute. Keep UNDEFINED. |
| } |
| |
| return channelAccess; |
| } |
| |
| /** Clears access rule cache and refresh tag. */ |
| public void reset() { |
| mRefreshTag = null; |
| mRuleCache.clear(); |
| mCarrierPrivilegeCache.clear(); |
| } |
| |
| /** Clears access rule cache only. */ |
| public void clearCache() { |
| mRuleCache.clear(); |
| mCarrierPrivilegeCache.clear(); |
| } |
| |
| /** Adds the Rule to the Cache */ |
| public void putWithMerge(REF_DO refDo, AR_DO arDo) { |
| if (refDo.isCarrierPrivilegeRefDo()) { |
| mCarrierPrivilegeCache.add(refDo); |
| return; |
| } |
| ChannelAccess channelAccess = mapArDo2ChannelAccess(arDo); |
| putWithMerge(refDo, channelAccess); |
| } |
| |
| /** Adds the Rule to the Cache */ |
| public void putWithMerge(REF_DO refDo, ChannelAccess channelAccess) { |
| if (refDo.isCarrierPrivilegeRefDo()) { |
| mCarrierPrivilegeCache.add(refDo); |
| return; |
| } |
| if (mRuleCache.containsKey(refDo)) { |
| ChannelAccess ca = mRuleCache.get(refDo); |
| |
| // if new ac condition is more restrictive then use their settings |
| // DENIED > ALLOWED > UNDEFINED |
| |
| if (ca.getAccess() != ChannelAccess.ACCESS.DENIED) { |
| if (channelAccess.getAccess() == ChannelAccess.ACCESS.DENIED) { |
| ca.setAccess(ChannelAccess.ACCESS.DENIED, channelAccess.getReason()); |
| } else if (channelAccess.getAccess() == ChannelAccess.ACCESS.ALLOWED) { |
| ca.setAccess(ChannelAccess.ACCESS.ALLOWED, ""); |
| } |
| } |
| |
| // Only the rule with the highest priority shall be applied if the rules conflict. |
| // NFC (NEVER) > NFC (ALWAYS) > No NFC attribute |
| |
| if (ca.getNFCEventAccess() != ChannelAccess.ACCESS.DENIED) { |
| if (channelAccess.getNFCEventAccess() == ChannelAccess.ACCESS.DENIED) { |
| ca.setNFCEventAccess(ChannelAccess.ACCESS.DENIED); |
| } else if (channelAccess.getNFCEventAccess() == ChannelAccess.ACCESS.ALLOWED) { |
| ca.setNFCEventAccess(ChannelAccess.ACCESS.ALLOWED); |
| } |
| } |
| |
| // Only the rule with the highest priority shall be applied if the rules conflict. |
| // APDU (NEVER) > APDU (Filter) > APDU (ALWAYS) > No APDU attribute |
| |
| if (ca.getApduAccess() != ChannelAccess.ACCESS.DENIED) { |
| if (channelAccess.getApduAccess() == ChannelAccess.ACCESS.DENIED) { |
| ca.setApduAccess(ChannelAccess.ACCESS.DENIED); |
| } else if (ca.isUseApduFilter() || channelAccess.isUseApduFilter()) { |
| // In order to differentiate APDU (Filter) from APDU (ALWAYS) clearly, |
| // check if the combined rule will have APDU filter here |
| // and avoid changing APDU access from UNDEFINED in APDU (Filter) case. |
| // APDU filters combination itself will be done in the next process below. |
| } else if (channelAccess.getApduAccess() == ChannelAccess.ACCESS.ALLOWED) { |
| ca.setApduAccess(ChannelAccess.ACCESS.ALLOWED); |
| } |
| } |
| |
| // put APDU filter together if resulting APDU access is not denied. |
| if (ca.getApduAccess() != ChannelAccess.ACCESS.DENIED) { |
| if (channelAccess.isUseApduFilter()) { |
| Log.i(mTag, "Merged Access Rule: APDU filter together"); |
| ca.setUseApduFilter(true); |
| ApduFilter[] filter = ca.getApduFilter(); |
| ApduFilter[] filter2 = channelAccess.getApduFilter(); |
| if (filter == null || filter.length == 0) { |
| ca.setApduFilter(filter2); |
| } else if (filter2 == null || filter2.length == 0) { |
| ca.setApduFilter(filter); |
| } else { |
| ApduFilter[] sum = new ApduFilter[filter.length + filter2.length]; |
| int i = 0; |
| for (ApduFilter f : filter) { |
| sum[i++] = f; |
| } |
| for (ApduFilter f : filter2) { |
| sum[i++] = f; |
| } |
| ca.setApduFilter(sum); |
| } |
| } |
| } else { |
| // if APDU access is not allowed the remove also all apdu filter. |
| ca.setUseApduFilter(false); |
| ca.setApduFilter(null); |
| } |
| if (DEBUG) { |
| Log.i(mTag, "Merged Access Rule: " + refDo.toString() + ", " + ca.toString()); |
| } |
| return; |
| } |
| if (DEBUG) { |
| Log.i(mTag, "Add Access Rule: " + refDo.toString() + ", " + channelAccess.toString()); |
| } |
| mRuleCache.put(refDo, channelAccess); |
| } |
| |
| /** Find Access Rule for the given AID and Application */ |
| public ChannelAccess findAccessRule(byte[] aid, List<byte[]> appCertHashes) |
| throws AccessControlException { |
| ChannelAccess ca = findAccessRuleInternal(aid, appCertHashes); |
| if (ca != null) { |
| if ((ca.getApduAccess() == ChannelAccess.ACCESS.UNDEFINED) && !ca.isUseApduFilter()) { |
| // Rule for APDU access does not exist. |
| // All the APDU access requests shall never be allowed in this case. |
| // This missing rule resolution is valid for both ARA and ARF |
| // if the supported GP SEAC version is v1.1 or later. |
| ca.setAccess(ChannelAccess.ACCESS.DENIED, "No APDU access rule is available"); |
| ca.setApduAccess(ChannelAccess.ACCESS.DENIED); |
| } |
| if (ca.getNFCEventAccess() == ChannelAccess.ACCESS.UNDEFINED) { |
| // Missing NFC access rule shall be treated as ALLOWED |
| // if relevant APDU access rule is ALLOWED or APDU filter is specified. |
| if (ca.isUseApduFilter()) { |
| ca.setNFCEventAccess(ChannelAccess.ACCESS.ALLOWED); |
| } else { |
| ca.setNFCEventAccess(ca.getApduAccess()); |
| } |
| } |
| // Note that the GP SEAC v1.1 has not been supported as GSMA TS.26 does not require it. |
| } |
| return ca; |
| } |
| |
| private ChannelAccess findAccessRuleInternal(byte[] aid, List<byte[]> appCertHashes) |
| throws AccessControlException { |
| |
| // TODO: check difference between DeviceCertHash and Certificate Chain (EndEntityCertHash, |
| // IntermediateCertHash (1..n), RootCertHash) |
| // The DeviceCertificate is equal to the EndEntityCertificate. |
| // The android systems seems always to deliver only the EndEntityCertificate, but this |
| // seems not |
| // to be sure. |
| // thats why we implement the whole chain. |
| |
| |
| /* Search Rule A ( Certificate(s); AID ) */ |
| AID_REF_DO aid_ref_do = getAidRefDo(aid); |
| REF_DO ref_do; |
| Hash_REF_DO hash_ref_do; |
| for (byte[] appCertHash : appCertHashes) { |
| hash_ref_do = new Hash_REF_DO(appCertHash); |
| ref_do = new REF_DO(aid_ref_do, hash_ref_do); |
| |
| if (mRuleCache.containsKey(ref_do)) { |
| if (DEBUG) { |
| Log.i(mTag, "findAccessRule() Case A " + ref_do.toString() + ", " |
| + mRuleCache.get(ref_do).toString()); |
| } |
| return mRuleCache.get(ref_do); |
| } |
| } |
| // no rule found, |
| // now we have to check if the given AID |
| // is used together with another specific hash value (another device application) |
| if (searchForRulesWithSpecificAidButOtherHash(aid_ref_do) != null) { |
| if (DEBUG) { |
| Log.i(mTag, "Conflict Resolution Case A returning access rule \'NEVER\'."); |
| } |
| ChannelAccess ca = new ChannelAccess(); |
| ca.setApduAccess(ChannelAccess.ACCESS.DENIED); |
| ca.setAccess(ChannelAccess.ACCESS.DENIED, |
| "AID has a specific access rule with a different hash. (Case A)"); |
| ca.setNFCEventAccess(ChannelAccess.ACCESS.DENIED); |
| return ca; |
| } |
| |
| // SearchRule B ( <AllDeviceApplications>; AID) |
| aid_ref_do = getAidRefDo(aid); |
| hash_ref_do = new Hash_REF_DO(); // empty hash ref |
| ref_do = new REF_DO(aid_ref_do, hash_ref_do); |
| |
| if (mRuleCache.containsKey(ref_do)) { |
| if (DEBUG) { |
| Log.i(mTag, "findAccessRule() Case B " + ref_do.toString() + ", " |
| + mRuleCache.get(ref_do).toString()); |
| } |
| return mRuleCache.get(ref_do); |
| } |
| |
| // Search Rule C ( Certificate(s); <AllSEApplications> ) |
| aid_ref_do = new AID_REF_DO(AID_REF_DO.TAG); |
| for (byte[] appCertHash : appCertHashes) { |
| hash_ref_do = new Hash_REF_DO(appCertHash); |
| ref_do = new REF_DO(aid_ref_do, hash_ref_do); |
| |
| if (mRuleCache.containsKey(ref_do)) { |
| if (DEBUG) { |
| Log.i(mTag, "findAccessRule() Case C " + ref_do.toString() + ", " |
| + mRuleCache.get(ref_do).toString()); |
| } |
| return mRuleCache.get(ref_do); |
| } |
| } |
| |
| // no rule found, |
| // now we have to check if the all AID DO |
| // is used together with another Hash |
| if (searchForRulesWithAllAidButOtherHash() != null) { |
| if (DEBUG) { |
| Log.i(mTag, "Conflict Resolution Case C returning access rule \'NEVER\'."); |
| } |
| ChannelAccess ca = new ChannelAccess(); |
| ca.setApduAccess(ChannelAccess.ACCESS.DENIED); |
| ca.setAccess( |
| ChannelAccess.ACCESS.DENIED, |
| "An access rule with a different hash and all AIDs was found. (Case C)"); |
| ca.setNFCEventAccess(ChannelAccess.ACCESS.DENIED); |
| return ca; |
| } |
| |
| // SearchRule D ( <AllDeviceApplications>; <AllSEApplications>) |
| aid_ref_do = new AID_REF_DO(AID_REF_DO.TAG); |
| hash_ref_do = new Hash_REF_DO(); |
| ref_do = new REF_DO(aid_ref_do, hash_ref_do); |
| |
| if (mRuleCache.containsKey(ref_do)) { |
| if (DEBUG) { |
| Log.i(mTag, "findAccessRule() Case D " + ref_do.toString() + ", " |
| + mRuleCache.get(ref_do).toString()); |
| } |
| return mRuleCache.get(ref_do); |
| } |
| |
| if (DEBUG) Log.i(mTag, "findAccessRule() not found"); |
| return null; |
| } |
| |
| /* |
| * The GP_SE_AC spec says: |
| * According to the rule conflict resolution process defined in section 3.2.1, if a specific |
| * rule exists |
| * that associates another device application with the SE application identified by AID (e.g. |
| * there is |
| * a rule associating AID with the hash of another device application), then the ARA-M (when |
| * using GET DATA [Specific]) or the Access Control Enforcer (when using GET DATA [All]) shall |
| * set the result of SearchRuleFor(DeviceApplicationCertificate, AID) to NEVER (i.e. precedence |
| * of specific rules over generic rules) |
| * |
| * In own words: |
| * Search the rules cache for a rule that contains the wanted AID but with another specific |
| * Hash value. |
| */ |
| private REF_DO searchForRulesWithSpecificAidButOtherHash(AID_REF_DO aidRefDo) { |
| |
| // AID has to be specific |
| if (aidRefDo == null) { |
| return null; |
| } |
| |
| // The specified AID_REF_DO does not have any AID and it is not for the default AID. |
| if (aidRefDo.getTag() == AID_REF_DO.TAG && aidRefDo.getAid().length == 0) { |
| return null; |
| } |
| |
| Set<REF_DO> keySet = mRuleCache.keySet(); |
| Iterator<REF_DO> iter = keySet.iterator(); |
| while (iter.hasNext()) { |
| REF_DO ref_do = iter.next(); |
| if (aidRefDo.equals(ref_do.getAidDo())) { |
| if (ref_do.getHashDo() != null |
| && ref_do.getHashDo().getHash().length > 0) { |
| // this ref_do contains the search AID and a specific hash value |
| return ref_do; |
| } |
| } |
| } |
| return null; |
| } |
| |
| /* |
| * The GP_SE_AC spec says: |
| * According to the rule conflict resolution process defined in section 3.2.1, if a specific |
| * rule exists |
| * that associates another device application with the SE application identified by AID (e.g. |
| * there is |
| * a rule associating AID with the hash of another device application), then the ARA-M (when |
| * using GET DATA [Specific]) or the Access Control Enforcer (when using GET DATA [All]) shall |
| * set the result of SearchRuleFor(DeviceApplicationCertificate, AID) to NEVER (i.e. precedence |
| * of specific rules over generic rules) |
| * |
| * In own words: |
| * Search the rules cache for a rule that contains a Hash with an all SE AID (4F 00). |
| */ |
| private Object searchForRulesWithAllAidButOtherHash() { |
| |
| AID_REF_DO aid_ref_do = new AID_REF_DO(AID_REF_DO.TAG); |
| |
| Set<REF_DO> keySet = mRuleCache.keySet(); |
| Iterator<REF_DO> iter = keySet.iterator(); |
| while (iter.hasNext()) { |
| REF_DO ref_do = iter.next(); |
| if (aid_ref_do.equals(ref_do.getAidDo())) { |
| // aid tlv is equal |
| if (ref_do.getHashDo() != null |
| && ref_do.getHashDo().getHash().length > 0) { |
| // return ref_do if |
| // a HASH value is available and has a length > 0 (SHA1_LEN) |
| return ref_do; |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** Check if the carrier privilege exists for the given package */ |
| public boolean checkCarrierPrivilege(String packageName, List<byte[]> appCertHashes) { |
| for (byte[] hash : appCertHashes) { |
| for (REF_DO ref_do : mCarrierPrivilegeCache) { |
| Hash_REF_DO hash_ref_do = ref_do.getHashDo(); |
| PKG_REF_DO pkg_ref_do = ref_do.getPkgDo(); |
| if (Hash_REF_DO.equals(hash_ref_do, new Hash_REF_DO(hash))) { |
| // If PKG_REF_DO exists then package name should match, otherwise allow |
| if (pkg_ref_do != null) { |
| if (packageName.equals(pkg_ref_do.getPackageName())) { |
| return true; |
| } |
| } else { |
| return true; |
| } |
| } |
| } |
| } |
| return false; |
| } |
| |
| /** Check if the given Refresh Tag is equal to the last known */ |
| public boolean isRefreshTagEqual(byte[] refreshTag) { |
| if (refreshTag == null || mRefreshTag == null) return false; |
| |
| return Arrays.equals(refreshTag, mRefreshTag); |
| } |
| |
| public byte[] getRefreshTag() { |
| return mRefreshTag; |
| } |
| |
| /** Sets the Refresh Tag */ |
| public void setRefreshTag(byte[] refreshTag) { |
| mRefreshTag = refreshTag; |
| } |
| |
| /** Debug information to be used by dumpsys */ |
| public void dump(PrintWriter writer) { |
| writer.println(mTag + ":"); |
| |
| /* Dump the refresh tag */ |
| writer.print("Current refresh tag is: "); |
| if (mRefreshTag == null) { |
| writer.print("<null>"); |
| } else { |
| for (byte oneByte : mRefreshTag) writer.printf("%02X:", oneByte); |
| } |
| writer.println(); |
| |
| /* Dump the rules cache */ |
| writer.println("Rules:"); |
| int i = 0; |
| for (Map.Entry<REF_DO, ChannelAccess> entry : mRuleCache.entrySet()) { |
| i++; |
| writer.print("rule " + i + ": "); |
| writer.println(entry.getKey().toString() + " -> " + entry.getValue().toString()); |
| } |
| writer.println(); |
| |
| /* Dump the Carrier Privilege cache */ |
| writer.println("Carrier Privilege:"); |
| i = 0; |
| for (REF_DO ref_do : mCarrierPrivilegeCache) { |
| i++; |
| writer.print("carrier privilege " + i + ": "); |
| writer.println(ref_do.toString()); |
| } |
| writer.println(); |
| } |
| } |