diff options
author | Justin Klaassen <justinklaassen@google.com> | 2018-04-03 23:21:57 -0400 |
---|---|---|
committer | Justin Klaassen <justinklaassen@google.com> | 2018-04-03 23:21:57 -0400 |
commit | 4d01eeaffaa720e4458a118baa137a11614f00f7 (patch) | |
tree | 66751893566986236788e3c796a7cc5e90d05f52 /android/se | |
parent | a192cc2a132cb0ee8588e2df755563ec7008c179 (diff) | |
download | android-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.java | 257 | ||||
-rw-r--r-- | android/se/omapi/Reader.java | 153 | ||||
-rw-r--r-- | android/se/omapi/SEService.java | 261 | ||||
-rw-r--r-- | android/se/omapi/Session.java | 307 |
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: + * [<first data byte>, ..., <last data byte>, <sw1>, <sw2>] + * + * @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: + * [<first data byte>, ..., <last data byte>, <sw1>, <sw2>] + * @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()); + } + } + } +} |