summaryrefslogtreecommitdiff
path: root/android/se
diff options
context:
space:
mode:
authorJustin Klaassen <justinklaassen@google.com>2018-04-03 23:21:57 -0400
committerJustin Klaassen <justinklaassen@google.com>2018-04-03 23:21:57 -0400
commit4d01eeaffaa720e4458a118baa137a11614f00f7 (patch)
tree66751893566986236788e3c796a7cc5e90d05f52 /android/se
parenta192cc2a132cb0ee8588e2df755563ec7008c179 (diff)
downloadandroid-28-4d01eeaffaa720e4458a118baa137a11614f00f7.tar.gz
Import Android SDK Platform P [4697573]
/google/data/ro/projects/android/fetch_artifact \ --bid 4697573 \ --target sdk_phone_armv7-win_sdk \ sdk-repo-linux-sources-4697573.zip AndroidVersion.ApiLevel has been modified to appear as 28 Change-Id: If80578c3c657366cc9cf75f8db13d46e2dd4e077
Diffstat (limited to 'android/se')
-rw-r--r--android/se/omapi/Channel.java257
-rw-r--r--android/se/omapi/Reader.java153
-rw-r--r--android/se/omapi/SEService.java261
-rw-r--r--android/se/omapi/Session.java307
4 files changed, 978 insertions, 0 deletions
diff --git a/android/se/omapi/Channel.java b/android/se/omapi/Channel.java
new file mode 100644
index 00000000..c8efede3
--- /dev/null
+++ b/android/se/omapi/Channel.java
@@ -0,0 +1,257 @@
+/*
+ * 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.
+ */
+/*
+ * Contributed by: Giesecke & Devrient GmbH.
+ */
+
+package android.se.omapi;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.util.Log;
+
+import java.io.IOException;
+
+/**
+ * Instances of this class represent an ISO/IEC 7816-4 channel opened to a
+ * Secure Element. It can be either a logical channel or the basic channel. They
+ * can be used to send APDUs to the secure element. Channels are opened by
+ * calling the Session.openBasicChannel(byte[]) or
+ * Session.openLogicalChannel(byte[]) methods.
+ *
+ * @see <a href="http://globalplatform.org">GlobalPlatform Open Mobile API</a>
+ */
+public class Channel {
+
+ private static final String TAG = "OMAPI.Channel";
+ private Session mSession;
+ private final ISecureElementChannel mChannel;
+ private final SEService mService;
+ private final Object mLock = new Object();
+
+ Channel(@NonNull SEService service, @NonNull Session session,
+ @NonNull ISecureElementChannel channel) {
+ if (service == null || session == null || channel == null) {
+ throw new IllegalArgumentException("Parameters cannot be null");
+ }
+ mService = service;
+ mSession = session;
+ mChannel = channel;
+ }
+
+ /**
+ * Closes this channel to the Secure Element. If the method is called when
+ * the channel is already closed, this method will be ignored. The close()
+ * method shall wait for completion of any pending transmit(byte[] command)
+ * before closing the channel.
+ */
+ public void close() {
+ if (!isClosed()) {
+ synchronized (mLock) {
+ try {
+ mChannel.close();
+ } catch (Exception e) {
+ Log.e(TAG, "Error closing channel", e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Tells if this channel is closed.
+ *
+ * @return <code>true</code> if the channel is closed or in case of an error.
+ * <code>false</code> otherwise.
+ */
+ public boolean isClosed() {
+ if (!mService.isConnected()) {
+ Log.e(TAG, "service not connected to system");
+ return true;
+ }
+ try {
+ return mChannel.isClosed();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Exception in isClosed()");
+ return true;
+ }
+ }
+
+ /**
+ * Returns a boolean telling if this channel is the basic channel.
+ *
+ * @return <code>true</code> if this channel is a basic channel. <code>false</code> if
+ * this channel is a logical channel.
+ */
+ public boolean isBasicChannel() {
+ if (!mService.isConnected()) {
+ throw new IllegalStateException("service not connected to system");
+ }
+ try {
+ return mChannel.isBasicChannel();
+ } catch (RemoteException e) {
+ throw new IllegalStateException(e.getMessage());
+ }
+ }
+
+ /**
+ * Transmit an APDU command (as per ISO/IEC 7816-4) to the Secure Element. The
+ * underlying layers generate as many TPDUs as necessary to transport this APDU. The
+ * API shall ensure that all available data returned from Secure Element, including
+ * concatenated responses, are retrieved and made available to the calling application. If a
+ * warning status code is received the API wont check for further response data but will
+ * return all data received so far and the warning status code.<br>
+ * The transport part is invisible from the application. The generated response is the
+ * response of the APDU which means that all protocols related responses are handled
+ * inside the API or the underlying implementation.<br>
+ * The transmit method shall support extended length APDU commands independently of
+ * the coding within the ATR.<br>
+ * For status word '61 XX' the API or underlying implementation shall issue a GET
+ * RESPONSE command as specified by ISO 7816-4 standard with LE=XX; for the status
+ * word '6C XX', the API or underlying implementation shall reissue the input command
+ * with LE=XX. For other status words, the API (or underlying implementation) shall return
+ * the complete response including data and status word to the device application. The API
+ * (or underlying implementation) shall not handle internally the received status words. The
+ * channel shall not be closed even if the Secure Element answered with an error code.
+ * The system ensures the synchronization between all the concurrent calls to this method,
+ * and that only one APDU will be sent at a time, irrespective of the number of TPDUs that
+ * might be required to transport it to the SE. The entire APDU communication to this SE is
+ * locked to the APDU.<br>
+ * The channel information in the class byte in the APDU will be ignored. The system will
+ * add any required information to ensure the APDU is transported on this channel.
+ * The only restrictions on the set of commands that can be sent is defined below, the API
+ * implementation shall be able to send all other commands: <br>
+ * <ul>
+ * <li>MANAGE_CHANNEL commands are not allowed.</li>
+ * <li>SELECT by DF Name (p1=04) are not allowed.</li>
+ * <li>CLA bytes with channel numbers are de-masked.</li>
+ * </ul>
+ *
+ * @param command the APDU command to be transmitted, as a byte array.
+ *
+ * @return the response received, as a byte array. The returned byte array contains the data
+ * bytes in the following order:
+ * [&lt;first data byte&gt;, ..., &lt;last data byte&gt;, &lt;sw1&gt;, &lt;sw2&gt;]
+ *
+ * @throws IOException if there is a communication problem to the reader or the Secure Element.
+ * @throws IllegalStateException if the channel is used after being closed.
+ * @throws IllegalArgumentException if the command byte array is less than 4 bytes long.
+ * @throws IllegalArgumentException if Lc byte is inconsistent with length of the byte array.
+ * @throws IllegalArgumentException if CLA byte is invalid according to [2] (0xff).
+ * @throws IllegalArgumentException if INS byte is invalid according to [2] (0x6x or 0x9x).
+ * @throws SecurityException if the command is filtered by the security policy.
+ * @throws NullPointerException if command is NULL.
+ */
+ public @NonNull byte[] transmit(@NonNull byte[] command) throws IOException {
+ if (!mService.isConnected()) {
+ throw new IllegalStateException("service not connected to system");
+ }
+ synchronized (mLock) {
+ try {
+ byte[] response = mChannel.transmit(command);
+ if (response == null) {
+ throw new IOException("Error in communicating with Secure Element");
+ }
+ return response;
+ } catch (ServiceSpecificException e) {
+ throw new IOException(e.getMessage());
+ } catch (RemoteException e) {
+ throw new IllegalStateException(e.getMessage());
+ }
+ }
+ }
+
+ /**
+ * Get the session that has opened this channel.
+ *
+ * @return the session object this channel is bound to.
+ */
+ public @NonNull Session getSession() {
+ return mSession;
+ }
+
+ /**
+ * Returns the data as received from the application select command inclusively the status word
+ * received at applet selection.
+ * The returned byte array contains the data bytes in the following order:
+ * [&lt;first data byte&gt;, ..., &lt;last data byte&gt;, &lt;sw1&gt;, &lt;sw2&gt;]
+ * @return The data as returned by the application select command inclusively the status word.
+ * Only the status word if the application select command has no returned data.
+ * Returns null if an application select command has not been performed or the selection
+ * response can not be retrieved by the reader implementation.
+ */
+ public @Nullable byte[] getSelectResponse() {
+ if (!mService.isConnected()) {
+ throw new IllegalStateException("service not connected to system");
+ }
+
+ byte[] response;
+ try {
+ response = mChannel.getSelectResponse();
+ } catch (RemoteException e) {
+ throw new IllegalStateException(e.getMessage());
+ }
+
+ if (response != null && response.length == 0) {
+ response = null;
+ }
+ return response;
+ }
+
+ /**
+ * Performs a selection of the next Applet on this channel that matches to the partial AID
+ * specified in the openBasicChannel(byte[] aid) or openLogicalChannel(byte[] aid) method.
+ * This mechanism can be used by a device application to iterate through all Applets
+ * matching to the same partial AID.
+ * If selectNext() returns true a new Applet was successfully selected on this channel.
+ * If no further Applet exists with matches to the partial AID this method returns false
+ * and the already selected Applet stays selected. <br>
+ *
+ * Since the API cannot distinguish between a partial and full AID the API shall rely on the
+ * response of the Secure Element for the return value of this method. <br>
+ * The implementation of the underlying SELECT command within this method shall use
+ * the same values as the corresponding openBasicChannel(byte[] aid) or
+ * openLogicalChannel(byte[] aid) command with the option: <br>
+ * P2='02' (Next occurrence) <br>
+ * The select response stored in the Channel object shall be updated with the APDU
+ * response of the SELECT command.
+
+ * @return <code>true</code> if new Applet was selected on this channel.
+ <code>false</code> he already selected Applet stays selected on this channel.
+ *
+ * @throws IOException if there is a communication problem to the reader or the Secure Element.
+ * @throws IllegalStateException if the channel is used after being closed.
+ * @throws UnsupportedOperationException if this operation is not supported by the card.
+ */
+ public boolean selectNext() throws IOException {
+ if (!mService.isConnected()) {
+ throw new IllegalStateException("service not connected to system");
+ }
+ try {
+ synchronized (mLock) {
+ return mChannel.selectNext();
+ }
+ } catch (ServiceSpecificException e) {
+ throw new IOException(e.getMessage());
+ } catch (RemoteException e) {
+ throw new IllegalStateException(e.getMessage());
+ }
+ }
+}
diff --git a/android/se/omapi/Reader.java b/android/se/omapi/Reader.java
new file mode 100644
index 00000000..9be3da6c
--- /dev/null
+++ b/android/se/omapi/Reader.java
@@ -0,0 +1,153 @@
+/*
+ * 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.
+ */
+/*
+ * Contributed by: Giesecke & Devrient GmbH.
+ */
+
+package android.se.omapi;
+
+import android.annotation.NonNull;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.util.Log;
+
+import java.io.IOException;
+
+/**
+ * Instances of this class represent Secure Element Readers supported to this
+ * device. These Readers can be physical devices or virtual devices. They can be
+ * removable or not. They can contain Secure Element that can or cannot be
+ * removed.
+ *
+ * @see <a href="http://globalplatform.org">GlobalPlatform Open Mobile API</a>
+ */
+public class Reader {
+
+ private static final String TAG = "OMAPI.Reader";
+ private final String mName;
+ private final SEService mService;
+ private ISecureElementReader mReader;
+ private final Object mLock = new Object();
+
+
+ Reader(@NonNull SEService service, @NonNull String name, @NonNull ISecureElementReader reader) {
+ if (reader == null || service == null || name == null) {
+ throw new IllegalArgumentException("Parameters cannot be null");
+ }
+ mName = name;
+ mService = service;
+ mReader = reader;
+ }
+
+ /**
+ * Return the name of this reader.
+ * <ul>
+ * <li>If this reader is a SIM reader, then its name must be "SIM[Slot]".</li>
+ * <li>If the reader is a SD or micro SD reader, then its name must be "SD[Slot]"</li>
+ * <li>If the reader is a embedded SE reader, then its name must be "eSE[Slot]"</li>
+ * </ul>
+ * Slot is a decimal number without leading zeros. The Numbering must start with 1
+ * (e.g. SIM1, SIM2, ... or SD1, SD2, ... or eSE1, eSE2, ...).
+ * The slot number “1” for a reader is optional
+ * (SIM and SIM1 are both valid for the first SIM-reader,
+ * but if there are two readers then the second reader must be named SIM2).
+ * This applies also for other SD or SE readers.
+ *
+ * @return the reader name, as a String.
+ */
+ public @NonNull String getName() {
+ return mName;
+ }
+
+ /**
+ * Connects to a Secure Element in this reader. <br>
+ * This method prepares (initialises) the Secure Element for communication
+ * before the Session object is returned (e.g. powers the Secure Element by
+ * ICC ON if its not already on). There might be multiple sessions opened at
+ * the same time on the same reader. The system ensures the interleaving of
+ * APDUs between the respective sessions.
+ *
+ * @throws IOException if something went wrong with the communicating to the
+ * Secure Element or the reader.
+ * @return a Session object to be used to create Channels.
+ */
+ public @NonNull Session openSession() throws IOException {
+ if (!mService.isConnected()) {
+ throw new IllegalStateException("service is not connected");
+ }
+
+ synchronized (mLock) {
+ ISecureElementSession session;
+ try {
+ session = mReader.openSession();
+ } catch (ServiceSpecificException e) {
+ throw new IOException(e.getMessage());
+ } catch (RemoteException e) {
+ throw new IllegalStateException(e.getMessage());
+ }
+ if (session == null) {
+ throw new IOException("service session is null.");
+ }
+ return new Session(mService, session, this);
+ }
+ }
+
+ /**
+ * Check if a Secure Element is present in this reader.
+ *
+ * @throws IllegalStateException if the service is not connected
+ * @return <code>true</code> if the SE is present, <code>false</code> otherwise.
+ */
+ public boolean isSecureElementPresent() {
+ if (!mService.isConnected()) {
+ throw new IllegalStateException("service is not connected");
+ }
+
+ try {
+ return mReader.isSecureElementPresent();
+ } catch (RemoteException e) {
+ throw new IllegalStateException("Error in isSecureElementPresent()");
+ }
+ }
+
+ /**
+ * Return the Secure Element service this reader is bound to.
+ *
+ * @return the SEService object.
+ */
+ public @NonNull SEService getSEService() {
+ return mService;
+ }
+
+ /**
+ * Close all the sessions opened on this reader.
+ * All the channels opened by all these sessions will be closed.
+ */
+ public void closeSessions() {
+ if (!mService.isConnected()) {
+ Log.e(TAG, "service is not connected");
+ return;
+ }
+ synchronized (mLock) {
+ try {
+ mReader.closeSessions();
+ } catch (RemoteException ignore) { }
+ }
+ }
+}
diff --git a/android/se/omapi/SEService.java b/android/se/omapi/SEService.java
new file mode 100644
index 00000000..311dc4c7
--- /dev/null
+++ b/android/se/omapi/SEService.java
@@ -0,0 +1,261 @@
+/*
+ * 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. All rights reserved.
+ */
+/*
+ * Contributed by: Giesecke & Devrient GmbH.
+ */
+
+package android.se.omapi;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.HashMap;
+
+/**
+ * The SEService realises the communication to available Secure Elements on the
+ * device. This is the entry point of this API. It is used to connect to the
+ * infrastructure and get access to a list of Secure Element Readers.
+ *
+ * @see <a href="http://simalliance.org">SIMalliance Open Mobile API v3.0</a>
+ */
+public class SEService {
+
+ /**
+ * Error code used with ServiceSpecificException.
+ * Thrown if there was an error communicating with the Secure Element.
+ *
+ * @hide
+ */
+ public static final int IO_ERROR = 1;
+
+ /**
+ * Error code used with ServiceSpecificException.
+ * Thrown if AID cannot be selected or is not available when opening
+ * a logical channel.
+ *
+ * @hide
+ */
+ public static final int NO_SUCH_ELEMENT_ERROR = 2;
+
+ /**
+ * Interface to send call-backs to the application when the service is connected.
+ */
+ public interface SecureElementListener {
+ /**
+ * Called by the framework when the service is connected.
+ */
+ void onServiceConnected();
+ }
+
+ /**
+ * Listener object that allows the notification of the caller if this
+ * SEService could be bound to the backend.
+ */
+ private class SEListener extends ISecureElementListener.Stub {
+ public SecureElementListener mListener = null;
+
+ @Override
+ public IBinder asBinder() {
+ return this;
+ }
+
+ public void onServiceConnected() {
+ if (mListener != null) {
+ mListener.onServiceConnected();
+ }
+ }
+ }
+ private SEListener mSEListener = new SEListener();
+
+ private static final String TAG = "OMAPI.SEService";
+
+ private final Object mLock = new Object();
+
+ /** The client context (e.g. activity). */
+ private final Context mContext;
+
+ /** The backend system. */
+ private volatile ISecureElementService mSecureElementService;
+
+ /**
+ * Class for interacting with the main interface of the backend.
+ */
+ private ServiceConnection mConnection;
+
+ /**
+ * Collection of available readers
+ */
+ private final HashMap<String, Reader> mReaders = new HashMap<String, Reader>();
+
+ /**
+ * Establishes a new connection that can be used to connect to all the
+ * Secure Elements available in the system. The connection process can be
+ * quite long, so it happens in an asynchronous way. It is usable only if
+ * the specified listener is called or if isConnected() returns
+ * <code>true</code>. <br>
+ * The call-back object passed as a parameter will have its
+ * onServiceConnected() method called when the connection actually happen.
+ *
+ * @param context
+ * the context of the calling application. Cannot be
+ * <code>null</code>.
+ * @param listener
+ * a SecureElementListener object.
+ */
+ public SEService(@NonNull Context context, @NonNull SecureElementListener listener) {
+
+ if (context == null) {
+ throw new NullPointerException("context must not be null");
+ }
+
+ mContext = context;
+ mSEListener.mListener = listener;
+
+ mConnection = new ServiceConnection() {
+
+ public synchronized void onServiceConnected(
+ ComponentName className, IBinder service) {
+
+ mSecureElementService = ISecureElementService.Stub.asInterface(service);
+ if (mSEListener != null) {
+ mSEListener.onServiceConnected();
+ }
+ Log.i(TAG, "Service onServiceConnected");
+ }
+
+ public void onServiceDisconnected(ComponentName className) {
+ mSecureElementService = null;
+ Log.i(TAG, "Service onServiceDisconnected");
+ }
+ };
+
+ Intent intent = new Intent(ISecureElementService.class.getName());
+ intent.setClassName("com.android.se",
+ "com.android.se.SecureElementService");
+ boolean bindingSuccessful =
+ mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+ if (bindingSuccessful) {
+ Log.i(TAG, "bindService successful");
+ }
+ }
+
+ /**
+ * Tells whether or not the service is connected.
+ *
+ * @return <code>true</code> if the service is connected.
+ */
+ public boolean isConnected() {
+ return mSecureElementService != null;
+ }
+
+ /**
+ * Returns the list of available Secure Element readers.
+ * There must be no duplicated objects in the returned list.
+ * All available readers shall be listed even if no card is inserted.
+ *
+ * @return The readers list, as an array of Readers. If there are no
+ * readers the returned array is of length 0.
+ */
+ public @NonNull Reader[] getReaders() {
+ if (mSecureElementService == null) {
+ throw new IllegalStateException("service not connected to system");
+ }
+ String[] readerNames;
+ try {
+ readerNames = mSecureElementService.getReaders();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+
+ Reader[] readers = new Reader[readerNames.length];
+ int i = 0;
+ for (String readerName : readerNames) {
+ if (mReaders.get(readerName) == null) {
+ try {
+ mReaders.put(readerName, new Reader(this, readerName,
+ getReader(readerName)));
+ readers[i++] = mReaders.get(readerName);
+ } catch (Exception e) {
+ Log.e(TAG, "Error adding Reader: " + readerName, e);
+ }
+ } else {
+ readers[i++] = mReaders.get(readerName);
+ }
+ }
+ return readers;
+ }
+
+ /**
+ * Releases all Secure Elements resources allocated by this SEService
+ * (including any binding to an underlying service).
+ * As a result isConnected() will return false after shutdown() was called.
+ * After this method call, the SEService object is not connected.
+ * It is recommended to call this method in the termination method of the calling application
+ * (or part of this application) which is bound to this SEService.
+ */
+ public void shutdown() {
+ synchronized (mLock) {
+ if (mSecureElementService != null) {
+ for (Reader reader : mReaders.values()) {
+ try {
+ reader.closeSessions();
+ } catch (Exception ignore) { }
+ }
+ }
+ try {
+ mContext.unbindService(mConnection);
+ } catch (IllegalArgumentException e) {
+ // Do nothing and fail silently since an error here indicates
+ // that binding never succeeded in the first place.
+ }
+ mSecureElementService = null;
+ }
+ }
+
+ /**
+ * Returns the version of the OpenMobile API specification this
+ * implementation is based on.
+ *
+ * @return String containing the OpenMobile API version (e.g. "3.0").
+ */
+ public @NonNull String getVersion() {
+ return "3.2";
+ }
+
+ @NonNull ISecureElementListener getListener() {
+ return mSEListener;
+ }
+
+ /**
+ * Obtain a Reader instance from the SecureElementService
+ */
+ private @NonNull ISecureElementReader getReader(String name) {
+ try {
+ return mSecureElementService.getReader(name);
+ } catch (RemoteException e) {
+ throw new IllegalStateException(e.getMessage());
+ }
+ }
+}
diff --git a/android/se/omapi/Session.java b/android/se/omapi/Session.java
new file mode 100644
index 00000000..adfeddd5
--- /dev/null
+++ b/android/se/omapi/Session.java
@@ -0,0 +1,307 @@
+/*
+ * 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) 2017, The Linux Foundation.
+ */
+/*
+ * Contributed by: Giesecke & Devrient GmbH.
+ */
+
+package android.se.omapi;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.NoSuchElementException;
+
+/**
+ * Instances of this class represent a connection session to one of the Secure
+ * Elements available on the device. These objects can be used to get a
+ * communication channel with an Applet in the Secure Element.
+ * This channel can be the basic channel or a logical channel.
+ *
+ * @see <a href="http://simalliance.org">SIMalliance Open Mobile API v3.0</a>
+ */
+public class Session {
+
+ private final Object mLock = new Object();
+ private final SEService mService;
+ private final Reader mReader;
+ private final ISecureElementSession mSession;
+ private static final String TAG = "OMAPI.Session";
+
+ Session(@NonNull SEService service, @NonNull ISecureElementSession session,
+ @NonNull Reader reader) {
+ if (service == null || reader == null || session == null) {
+ throw new IllegalArgumentException("Parameters cannot be null");
+ }
+ mService = service;
+ mReader = reader;
+ mSession = session;
+ }
+
+ /**
+ * Get the reader that provides this session.
+ *
+ * @return The Reader object.
+ */
+ public @NonNull Reader getReader() {
+ return mReader;
+ }
+
+ /**
+ * Get the Answer to Reset of this Secure Element. <br>
+ * The returned byte array can be null if the ATR for this Secure Element is
+ * not available.
+ *
+ * @throws IllegalStateException if there was an error connecting to SE or
+ * if the service was not connected.
+ * @return the ATR as a byte array or null.
+ */
+ public @Nullable byte[] getATR() {
+ if (!mService.isConnected()) {
+ throw new IllegalStateException("service not connected to system");
+ }
+ try {
+ return mSession.getAtr();
+ } catch (RemoteException e) {
+ throw new IllegalStateException(e.getMessage());
+ }
+ }
+
+ /**
+ * Close the connection with the Secure Element. This will close any
+ * channels opened by this application with this Secure Element.
+ */
+ public void close() {
+ if (!mService.isConnected()) {
+ Log.e(TAG, "service not connected to system");
+ return;
+ }
+ synchronized (mLock) {
+ try {
+ mSession.close();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error closing session", e);
+ }
+ }
+ }
+
+ /**
+ * Tells if this session is closed.
+ *
+ * @return <code>true</code> if the session is closed, false otherwise.
+ */
+ public boolean isClosed() {
+ try {
+ return mSession.isClosed();
+ } catch (RemoteException e) {
+ // If there was an error here, then the session is considered close
+ return true;
+ }
+ }
+
+ /**
+ * Close any channel opened on this session.
+ */
+ public void closeChannels() {
+ if (!mService.isConnected()) {
+ Log.e(TAG, "service not connected to system");
+ return;
+ }
+
+ synchronized (mLock) {
+ try {
+ mSession.closeChannels();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error closing channels", e);
+ }
+ }
+ }
+
+ /**
+ * Get an access to the basic channel, as defined in the ISO/IEC 7816-4 specification (the
+ * one that has number 0). The obtained object is an instance of the Channel class.
+ * If the AID is null, it means no Applet is to be selected on this channel and the default
+ * Applet is used. If the AID is defined then the corresponding Applet is selected.
+ * Once this channel has been opened by a device application, it is considered as "locked"
+ * by this device application, and other calls to this method will return null, until the
+ * channel is closed. Some Secure Elements (like the UICC) might always keep the basic channel
+ * locked (i.e. return null to applications), to prevent access to the basic channel, while
+ * some other might return a channel object implementing some kind of filtering on the
+ * commands, restricting the set of accepted command to a smaller set.
+ * It is recommended for the UICC to reject the opening of the basic channel to a specific
+ * applet, by always answering null to such a request.
+ * For other Secure Elements, the recommendation is to accept opening the basic channel
+ * on the default applet until another applet is selected on the basic channel. As there is no
+ * other way than a reset to select again the default applet, the implementation of the
+ * transport API should guarantee that the openBasicChannel(null) command will return
+ * null until a reset occurs.
+ * With previous release (V2.05) it was not possible to set P2 value, this value was always
+ * set to '00'.Except for specific needs it is recommended to keep P2 to '00'. It is
+ * recommended that the device allows all values for P2, however only the following values
+ * are mandatory: '00', '04', '08', '0C'(as defined in [2])
+ * The implementation of the underlying SELECT command within this method shall be
+ * based on ISO 7816-4 with following options:
+ * <ul>
+ * <li>CLA = '00'</li>
+ * <li>INS = 'A4'</li>
+ * <li>P1 = '04' (Select by DF name/application identifier)</li>
+ * </ul>
+ *
+ * The select response data can be retrieved with byte[] getSelectResponse().
+ * The API shall handle received status word as follow. If the status word indicates that the
+ * Secure Element was able to open a channel (e.g. status word '90 00' or status words
+ * referencing a warning in ISO-7816-4: '62 XX' or '63 XX') the API shall keep the
+ * channel opened and the next getSelectResponse() shall return the received status
+ * word.
+ * Other received status codes indicating that the Secure Element was able not to open a
+ * channel shall be considered as an error and the corresponding channel shall not be
+ * opened.
+ * The function without P2 as parameter is provided for backwards compatibility and will
+ * fall back to a select command with P2='00'.
+ *
+ * @param aid the AID of the Applet to be selected on this channel, as a
+ * byte array, or null if no Applet is to be selected.
+ * @param p2 the P2 parameter of the SELECT APDU executed on this channel.
+ * @throws IOException if there is a communication problem to the reader or
+ * the Secure Element.
+ * @throws IllegalStateException if the Secure Element session is used after
+ * being closed.
+ * @throws IllegalArgumentException if the aid's length is not within 5 to
+ * 16 (inclusive).
+ * @throws SecurityException if the calling application cannot be granted
+ * access to this AID or the default Applet on this
+ * session.
+ * @throws NoSuchElementException if the AID on the Secure Element is not available or cannot be
+ * selected.
+ * @throws UnsupportedOperationException if the given P2 parameter is not
+ * supported by the device
+ * @return an instance of Channel if available or null.
+ */
+ public @Nullable Channel openBasicChannel(@Nullable byte[] aid, @Nullable byte p2)
+ throws IOException {
+ if (!mService.isConnected()) {
+ throw new IllegalStateException("service not connected to system");
+ }
+
+ synchronized (mLock) {
+ try {
+ ISecureElementChannel channel = mSession.openBasicChannel(aid, p2,
+ mReader.getSEService().getListener());
+ if (channel == null) {
+ return null;
+ }
+ return new Channel(mService, this, channel);
+ } catch (ServiceSpecificException e) {
+ if (e.errorCode == SEService.IO_ERROR) {
+ throw new IOException(e.getMessage());
+ } else if (e.errorCode == SEService.NO_SUCH_ELEMENT_ERROR) {
+ throw new NoSuchElementException(e.getMessage());
+ } else {
+ throw new IllegalStateException(e.getMessage());
+ }
+ } catch (RemoteException e) {
+ throw new IllegalStateException(e.getMessage());
+ }
+ }
+ }
+
+ /**
+ * Open a logical channel with the Secure Element, selecting the Applet represented by
+ * the given AID. If the AID is null, which means no Applet is to be selected on this
+ * channel, the default Applet is used. It's up to the Secure Element to choose which
+ * logical channel will be used.
+ * With previous release (V2.05) it was not possible to set P2 value, this value was always
+ * set to '00'.Except for specific needs it is recommended to keep P2 to '00'. It is
+ * recommended that the device allows all values for P2, however only the following values
+ * are mandatory: '00', '04', '08', '0C'(as defined in [2])
+ * The implementation of the underlying SELECT command within this method shall be
+ * based on ISO 7816-4 with following options:
+ *
+ * <ul>
+ * <li>CLA = '01' to '03', '40 to 4F'</li>
+ * <li>INS = 'A4'</li>
+ * <li>P1 = '04' (Select by DF name/application identifier)</li>
+ * </ul>
+ *
+ * The select response data can be retrieved with byte[] getSelectResponse().
+ * The API shall handle received status word as follow. If the status word indicates that the
+ * Secure Element was able to open a channel (e.g. status word '90 00' or status words
+ * referencing a warning in ISO-7816-4: '62 XX' or '63 XX') the API shall keep the
+ * channel opened and the next getSelectResponse() shall return the received status
+ * word.
+ * Other received status codes indicating that the Secure Element was able not to open a
+ * channel shall be considered as an error and the corresponding channel shall not be
+ * opened.
+ * In case of UICC it is recommended for the API to reject the opening of the logical
+ * channel without a specific AID, by always answering null to such a request.
+ * The function without P2 as parameter is provided for backwards compatibility and will
+ * fall back to a select command with P2=00.
+ *
+ * @param aid the AID of the Applet to be selected on this channel, as a
+ * byte array.
+ * @param p2 the P2 parameter of the SELECT APDU executed on this channel.
+ * @throws IOException if there is a communication problem to the reader or
+ * the Secure Element.
+ * @throws IllegalStateException if the Secure Element is used after being
+ * closed.
+ * @throws IllegalArgumentException if the aid's length is not within 5 to
+ * 16 (inclusive).
+ * @throws SecurityException if the calling application cannot be granted
+ * access to this AID or the default Applet on this
+ * session.
+ * @throws NoSuchElementException if the AID on the Secure Element is not
+ * available or cannot be selected or a logical channel is already
+ * open to a non-multiselectable Applet.
+ * @throws UnsupportedOperationException if the given P2 parameter is not
+ * supported by the device.
+ * @return an instance of Channel. Null if the Secure Element is unable to
+ * provide a new logical channel.
+ */
+ public @Nullable Channel openLogicalChannel(@Nullable byte[] aid, @Nullable byte p2)
+ throws IOException {
+ if (!mService.isConnected()) {
+ throw new IllegalStateException("service not connected to system");
+ }
+ synchronized (mLock) {
+ try {
+ ISecureElementChannel channel = mSession.openLogicalChannel(
+ aid,
+ p2,
+ mReader.getSEService().getListener());
+ if (channel == null) {
+ return null;
+ }
+ return new Channel(mService, this, channel);
+ } catch (ServiceSpecificException e) {
+ if (e.errorCode == SEService.IO_ERROR) {
+ throw new IOException(e.getMessage());
+ } else if (e.errorCode == SEService.NO_SUCH_ELEMENT_ERROR) {
+ throw new NoSuchElementException(e.getMessage());
+ } else {
+ throw new IllegalStateException(e.getMessage());
+ }
+ } catch (RemoteException e) {
+ throw new IllegalStateException(e.getMessage());
+ }
+ }
+ }
+}