blob: 22b82f05970c16231a1e74e14b3b5fc7e3737f50 [file] [log] [blame]
/*
* 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) 2014-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.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.Signature;
import android.os.Build;
import android.os.SystemProperties;
import android.util.Log;
import com.android.se.Channel;
import com.android.se.SecureElementService;
import com.android.se.Terminal;
import com.android.se.internal.ByteArrayConverter;
import com.android.se.security.ChannelAccess.ACCESS;
import com.android.se.security.ara.AraController;
import com.android.se.security.arf.ArfController;
import java.io.IOException;
import java.io.PrintWriter;
import java.security.AccessControlException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.MissingResourceException;
import java.util.NoSuchElementException;
/** Reads and Maintains the ARF and ARA access control for a particular Secure Element */
public class AccessControlEnforcer {
private final String mTag = "SecureElement-AccessControlEnforcer";
private static final boolean DEBUG = Build.IS_DEBUGGABLE;
private PackageManager mPackageManager = null;
private boolean mNoRuleFound = false;
private AraController mAraController = null;
private boolean mUseAra = true;
private ArfController mArfController = null;
private boolean mUseArf = false;
private AccessRuleCache mAccessRuleCache = null;
private boolean mRulesRead = false;
private Terminal mTerminal = null;
private ChannelAccess mInitialChannelAccess = new ChannelAccess();
private boolean mFullAccess = false;
public AccessControlEnforcer(Terminal terminal) {
mTerminal = terminal;
mAccessRuleCache = new AccessRuleCache();
}
public static byte[] getDefaultAccessControlAid() {
return AraController.getAraMAid();
}
public PackageManager getPackageManager() {
return mPackageManager;
}
public void setPackageManager(PackageManager packageManager) {
mPackageManager = packageManager;
}
public Terminal getTerminal() {
return mTerminal;
}
public AccessRuleCache getAccessRuleCache() {
return mAccessRuleCache;
}
/** Resets the Access Control for the Secure Element */
public synchronized void reset() {
// Destroy any previous Controler
// in order to reset the ACE
Log.i(mTag, "Reset the ACE for terminal:" + mTerminal.getName());
mAccessRuleCache.reset();
mAraController = null;
mArfController = null;
}
/** Initializes the Access Control for the Secure Element */
public synchronized void initialize() throws IOException, MissingResourceException {
boolean status = true;
String denyMsg = "";
// allow access to set up access control for a channel
mInitialChannelAccess.setApduAccess(ChannelAccess.ACCESS.ALLOWED);
mInitialChannelAccess.setNFCEventAccess(ChannelAccess.ACCESS.ALLOWED);
mInitialChannelAccess.setAccess(ChannelAccess.ACCESS.ALLOWED, "");
readSecurityProfile();
mNoRuleFound = false;
// 1 - Let's try to use ARA
if (mUseAra && mAraController == null) {
mAraController = new AraController(mAccessRuleCache, mTerminal);
}
if (mUseAra && mAraController != null) {
try {
mAraController.initialize();
Log.i(mTag, "ARA applet is used for:" + mTerminal.getName());
// disable other access methods
mUseArf = false;
mFullAccess = false;
} catch (IOException | MissingResourceException e) {
throw e;
} catch (Exception e) {
// ARA cannot be used since we got an exception during initialization
mUseAra = false;
denyMsg = e.getLocalizedMessage();
if (e instanceof NoSuchElementException) {
Log.i(mTag, "No ARA applet found in: " + mTerminal.getName());
if (!mUseArf) {
// ARA does not exist on the secure element right now,
// but it might be installed later.
mNoRuleFound = true;
status = mFullAccess;
}
} else if (mTerminal.getName().startsWith(SecureElementService.UICC_TERMINAL)) {
// A possible explanation could simply be due to the fact that the UICC is old
// and does not support logical channel (and is not compliant with GP spec).
// We should simply act as if no ARA was available in this case.
if (!mUseArf) {
// Only ARA was the candidate to retrieve access rules,
// but it is not 100% sure if the expected ARA really does not exist.
// Full access should not be granted in this case.
mFullAccess = false;
status = false;
}
} else {
// ARA is available but doesn't work properly.
// We are going to disable everything per security req.
mUseArf = false;
mFullAccess = false;
status = false;
Log.i(mTag, "Problem accessing ARA, Access DENIED "
+ e.getLocalizedMessage());
}
}
}
// 2 - Let's try to use ARF since ARA cannot be used
if (mUseArf && mArfController == null) {
mArfController = new ArfController(mAccessRuleCache, mTerminal);
}
if (mUseArf && mArfController != null) {
try {
mArfController.initialize();
// disable other access methods
Log.i(mTag, "ARF rules are used for:" + mTerminal.getName());
mFullAccess = false;
} catch (IOException | MissingResourceException e) {
throw e;
} catch (Exception e) {
// ARF cannot be used since we got an exception
mUseArf = false;
denyMsg = e.getLocalizedMessage();
Log.e(mTag, e.getMessage());
if (e instanceof NoSuchElementException) {
Log.i(mTag, "No ARF found in: " + mTerminal.getName());
// ARF does not exist on the secure element right now,
// but it might be added later.
mNoRuleFound = true;
status = mFullAccess;
} else {
// It is not 100% sure if the expected ARF really does not exist.
// No ARF might be due to a kind of temporary problem,
// so full access should not be granted in this case.
mFullAccess = false;
status = false;
}
}
}
/* 4 - Let's block everything since neither ARA, ARF or fullaccess can be used */
if (!mUseArf && !mUseAra && !mFullAccess) {
mInitialChannelAccess.setApduAccess(ChannelAccess.ACCESS.DENIED);
mInitialChannelAccess.setNFCEventAccess(ChannelAccess.ACCESS.DENIED);
mInitialChannelAccess.setAccess(ChannelAccess.ACCESS.DENIED, denyMsg);
Log.i(mTag, "Deny any access to:" + mTerminal.getName());
}
mRulesRead = status;
}
/**
* Returns the result of the previous attempt to select ARA and/or ARF.
*
* @return true if no rule was found in the previous attempt.
*/
public boolean isNoRuleFound() {
return mNoRuleFound;
}
/** Check if the Channel has permission for the given APDU */
public synchronized void checkCommand(Channel channel, byte[] command) {
ChannelAccess ca = channel.getChannelAccess();
if (ca == null) {
throw new AccessControlException(mTag + "Channel access not set");
}
String reason = ca.getReason();
if (reason.length() == 0) {
reason = "Unspecified";
}
if (DEBUG) {
Log.i(mTag, "checkCommand() : Access = " + ca.getAccess() + " APDU Access = "
+ ca.getApduAccess() + " Reason = " + reason);
}
if (ca.getAccess() != ACCESS.ALLOWED) {
throw new AccessControlException(mTag + reason);
}
if (ca.isUseApduFilter()) {
ApduFilter[] accessConditions = ca.getApduFilter();
if (accessConditions == null || accessConditions.length == 0) {
throw new AccessControlException(mTag + "Access Rule not available:"
+ reason);
}
for (ApduFilter ac : accessConditions) {
if (CommandApdu.compareHeaders(command, ac.getMask(), ac.getApdu())) {
return;
}
}
throw new AccessControlException(mTag + "Access Rule does not match: "
+ reason);
}
if (ca.getApduAccess() == ChannelAccess.ACCESS.ALLOWED) {
return;
} else {
throw new AccessControlException(mTag + "APDU access NOT allowed");
}
}
/** Sets up the Channel Access for the given Package */
public ChannelAccess setUpChannelAccess(byte[] aid, String packageName, boolean checkRefreshTag)
throws IOException, MissingResourceException {
ChannelAccess channelAccess = null;
// check result of channel access during initialization procedure
if (mInitialChannelAccess.getAccess() == ChannelAccess.ACCESS.DENIED) {
throw new AccessControlException(
mTag + "access denied: " + mInitialChannelAccess.getReason());
}
// this is the new GP Access Control Enforcer implementation
if (mUseAra || mUseArf) {
channelAccess = internal_setUpChannelAccess(aid, packageName,
checkRefreshTag);
}
if (channelAccess == null || (channelAccess.getApduAccess() != ChannelAccess.ACCESS.ALLOWED
&& !channelAccess.isUseApduFilter())) {
if (mFullAccess) {
// if full access is set then we reuse the initial channel access,
// since we got so far it allows everything with a descriptive reason.
channelAccess = mInitialChannelAccess;
} else {
throw new AccessControlException(mTag + "no APDU access allowed!");
}
}
channelAccess.setPackageName(packageName);
return channelAccess.clone();
}
private synchronized ChannelAccess internal_setUpChannelAccess(byte[] aid,
String packageName, boolean checkRefreshTag) throws IOException,
MissingResourceException {
if (packageName == null || packageName.isEmpty()) {
throw new AccessControlException("package names must be specified");
}
try {
// estimate SHA-1 and SHA-256 hash values of the device application's certificate.
List<byte[]> appCertHashes = getAppCertHashes(packageName);
// APP certificates must be available => otherwise Exception
if (appCertHashes == null || appCertHashes.size() == 0) {
throw new AccessControlException(
"Application certificates are invalid or do not exist.");
}
if (checkRefreshTag) {
updateAccessRuleIfNeed();
}
return getAccessRule(aid, appCertHashes);
} catch (IOException | MissingResourceException e) {
throw e;
} catch (Throwable exp) {
throw new AccessControlException(exp.getMessage());
}
}
/** Fetches the Access Rules for the given application and AID pair */
public ChannelAccess getAccessRule(
byte[] aid, List<byte []> appCertHashes)
throws AccessControlException {
if (DEBUG) {
for (byte[] appCertHash : appCertHashes) {
Log.i(mTag, "getAccessRule() appCert = "
+ ByteArrayConverter.byteArrayToHexString(appCertHash));
}
}
ChannelAccess channelAccess = null;
// if read all is true get rule from cache.
if (mRulesRead) {
// get rules from internal storage
channelAccess = mAccessRuleCache.findAccessRule(aid, appCertHashes);
}
// if no rule was found return an empty access rule
// with all access denied.
if (channelAccess == null) {
channelAccess = new ChannelAccess();
channelAccess.setAccess(ChannelAccess.ACCESS.DENIED, "no access rule found!");
channelAccess.setApduAccess(ChannelAccess.ACCESS.DENIED);
channelAccess.setNFCEventAccess(ChannelAccess.ACCESS.DENIED);
}
return channelAccess;
}
/**
* Returns hashes of certificate chain for one package.
*/
private List<byte[]> getAppCertHashes(String packageName)
throws NoSuchAlgorithmException, AccessControlException {
if (packageName == null || packageName.length() == 0) {
throw new AccessControlException("Package Name not defined");
}
PackageInfo foundPkgInfo;
try {
foundPkgInfo = mPackageManager.getPackageInfo(packageName,
PackageManager.GET_SIGNATURES);
} catch (NameNotFoundException ne) {
throw new AccessControlException("Package does not exist");
}
if (foundPkgInfo == null) {
throw new AccessControlException("Package does not exist");
}
MessageDigest md = MessageDigest.getInstance("SHA");
MessageDigest md256 = MessageDigest.getInstance("SHA-256");
if (md == null || md256 == null) {
throw new AccessControlException("Hash can not be computed");
}
List<byte[]> appCertHashes = new ArrayList<byte[]>();
for (Signature signature : foundPkgInfo.signatures) {
appCertHashes.add(md.digest(signature.toByteArray()));
appCertHashes.add(md256.digest(signature.toByteArray()));
}
return appCertHashes;
}
/** Returns true if the given application is allowed to recieve NFC Events */
public synchronized boolean[] isNfcEventAllowed(byte[] aid,
String[] packageNames) {
if (mUseAra || mUseArf) {
return internal_isNfcEventAllowed(aid, packageNames);
} else {
// if ARA and ARF is not available and
// - terminal DOES NOT belong to a UICC -> mFullAccess is true
// - terminal belongs to a UICC -> mFullAccess is false
boolean[] ret = new boolean[packageNames.length];
for (int i = 0; i < ret.length; i++) {
ret[i] = mFullAccess;
}
return ret;
}
}
private synchronized boolean[] internal_isNfcEventAllowed(byte[] aid,
String[] packageNames) {
int i = 0;
boolean[] nfcEventFlags = new boolean[packageNames.length];
for (String packageName : packageNames) {
// estimate hash value of the device application's certificate.
try {
List<byte[]> appCertHashes = getAppCertHashes(packageName);
// APP certificates must be available => otherwise Exception
if (appCertHashes == null || appCertHashes.size() == 0) {
nfcEventFlags[i] = false;
} else {
ChannelAccess channelAccess = getAccessRule(aid, appCertHashes);
nfcEventFlags[i] =
(channelAccess.getNFCEventAccess() == ChannelAccess.ACCESS.ALLOWED);
}
} catch (Exception e) {
Log.w(mTag, " Access Rules for NFC: " + e.getLocalizedMessage());
nfcEventFlags[i] = false;
}
i++;
}
return nfcEventFlags;
}
private void updateAccessRuleIfNeed() throws IOException {
if (mUseAra && mAraController != null) {
try {
mAraController.initialize();
mUseArf = false;
mFullAccess = false;
} catch (IOException | MissingResourceException e) {
// There was a communication error between the terminal and the secure element
// or failure in retrieving rules due to the lack of a new logical channel.
// These errors must be distinguished from other ones.
throw e;
} catch (Exception e) {
throw new AccessControlException("No ARA applet found in " + mTerminal.getName());
}
} else if (mUseArf && mArfController != null) {
try {
mArfController.initialize();
} catch (IOException | MissingResourceException e) {
// There was a communication error between the terminal and the secure element
// or failure in retrieving rules due to the lack of a new logical channel.
// These errors must be distinguished from other ones.
throw e;
} catch (Exception e) {
throw new AccessControlException("No ARF found in " + mTerminal.getName());
}
}
}
/** Debug information to be used by dumpsys */
public void dump(PrintWriter writer) {
writer.println(mTag + ":");
writer.println("mUseArf: " + mUseArf);
writer.println("mUseAra: " + mUseAra);
writer.println("mInitialChannelAccess:");
writer.println(mInitialChannelAccess.toString());
writer.println();
/* Dump the access rule cache */
if (mAccessRuleCache != null) mAccessRuleCache.dump(writer);
}
private void readSecurityProfile() {
if (!Build.IS_DEBUGGABLE) {
mUseArf = true;
mUseAra = true;
mFullAccess = false; // Per default we don't grant full access.
} else {
String level = SystemProperties.get("service.seek", "useara usearf");
level = SystemProperties.get("persist.service.seek", level);
if (level.contains("usearf")) {
mUseArf = true;
} else {
mUseArf = false;
}
if (level.contains("useara")) {
mUseAra = true;
} else {
mUseAra = false;
}
if (level.contains("fullaccess")) {
mFullAccess = true;
} else {
mFullAccess = false;
}
}
if (!mTerminal.getName().startsWith(SecureElementService.UICC_TERMINAL)) {
// It shall be allowed to grant full access if no rule can be retrieved
// from the secure element except for UICC.
mFullAccess = true;
// ARF is supported only on UICC.
mUseArf = false;
}
Log.i(
mTag,
"Allowed ACE mode: ara=" + mUseAra + " arf=" + mUseArf + " fullaccess="
+ mFullAccess);
}
}