Usage¶
Middleware as an embedded library¶
The application must include the scmJavaApi.aar
library appropriately.
Be aware that the aar
library declares the following permissions (which will be merged with the target
application permissions):
android.permission.INTERNET
android.permission.NFC
Note that the android.permission.INTERNET
permission is not used to access some external server. It is
required because the middleware listens to a local TCP port, as it uses sockets as an internal communication mean.
This network communication is done between a local HTTP server and the rest of the API in current used thread.
For this reason, to allow the use of the library in main thread, these following lines are called at library initialization:
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
Configuration¶
Before using the library, configuring your application must be necessary.
Library dependencies¶
scmJavaApi.aar
library needs some dependencies to work correctly. As the
library is not in a maven repository, application must define following dependencies
in build.gradle
file:
dependencies {
...
// needed dependencies:
implementation "androidx.lifecycle:lifecycle-common-java8:2.3.1"
implementation "androidx.appcompat:appcompat:1.3.1"
}
You can change the version of dependencies for newer ones.
Network security¶
Using properly the library requires to allow clear text network communication with internal HTTP server on localhost.
A way to do that is to follow these 2 steps:
in your application
AndroidManifest.xml
file, define aandroid:networkSecurityConfig
entry:<?xml version="1.0" encoding="utf-8"?> <manifest ... > <application android:networkSecurityConfig="@xml/network_security_config" ... > ... </application> </manifest>create a
network_security_config.xml
file inapp/src/main/res/xml
directory:<?xml version="1.0" encoding="utf-8"?> <network-security-config> <domain-config cleartextTrafficPermitted="true"> <domain includeSubdomains="false">127.0.0.1</domain> </domain-config> </network-security-config>
Warning
This step is necessary to use all the API features in your application.
Card readers¶
To support the different card reader types, some configuration is needed. All these changes must be done
in your application AndroidManifest.xml
file.
NFC readers:
To use NFC in your application, <uses-feature android:name="android.hardware.nfc" />
must be added in manifest
file. As explained previously, permission for NFC use is already declared by the aar
library.
USB readers:
To use USB readers, <uses-feature android:name="android.hardware.usb.host" />
must be defined.
To be able to read USB Smart Card readers inside the app, an activity must be added in the manifest:
<activity android:name="_package_name_.UsbConnectorActivity"
tools:replace="android:enabled"
android:enabled="true">
</activity>
The UsbConnectorActivity.java file contains the following code:
package _package_name_
import ...
public class UsbConnectorActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_usb_connector);
SCMHelper.observableActivityCreated(this);
finish();
}
}
On first USB device connection, Android will display usb permissions checkbox: “always open [app] when [USB reader name] is connected”. Once the user checks this and confirms it, each time this USB device will be connected, this activity will be created and destroyed immediately.
Bluetooth readers:
To use of Bluetooth readers, permissions must be added. The required permissions are different depending on the Android SDK version.
These permissions can be added with the following lines:
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" android:maxSdkVersion="30"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" android:minSdkVersion="31"/>
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" android:minSdkVersion="31" />
In addition, <uses-feature android:name="android.hardware.bluetooth_le" />
must be defined.
Quick start: obtaining a Token
¶
The initialization of a Token requires two components to be set from the library:
- Internal software components (card system, SCM HTTP server, Idopte cache…) should be initialized. This is done by
using
SCMHelper.activityCreated
,SCMHelper.observableActivityCreated
or directlySCMActivity
class. This needs to be done every time an activity using the API is entered (typically in the onCreate method). The API can be accessed by only one activity at a time.
class TokenModel extends ViewModel implements ReaderEvents {
[...]
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
[...]
SCMHelper.observableActivityCreated(this);
[...]
}
}
- The
SCMEmbedEnvironment
(and more generallySCMEnvironment
) will receive token event updates from the SCM HTTP server, and notify anyReaderEvents
. The instantiation is typically done inside a ViewModel that register itself withSCMEnvironment.addReaderEventListener
.
class TokenModel extends ViewModel implements ReaderEvents {
private final List<Token> tokenlist = new ArrayList();
private final SCMEnvironment env;
public TokenModel() throws SCMException {
this.env = SCMEmbedEnvironment.getSCMEmbedEnvironment();
this.env.addReaderEventListener(this);
for (Reader r : env.getReaders())
onReaderAdded(r);
}
@Override
public void onTokenRemoval(Reader reader) {
updateTokens(reader);
}
@Override
public void onTokenArrival(Reader reader) {
updateTokens(reader);
}
private void updateTokens(Reader reader) {
// connect/disconnect token, do cryptographic operations...
}
}
After this, the first token interacting with the device should trigger onTokenArrival(Reader reader)
.
tokenlist
can be populated if the connection is successful:
try {
tokenlist.add(reader.connect());
} catch (SCMException e) {
Log.e("SCM", "Could not retrieve token", e);
}
Other example¶
If no card is found, the application may want to listen for reader events, and wait for a card to be
found within the NFC field. The code below shows a possible implementation for such a mechanism,
calling the processToken
method when a smart card has been found:
Token token = null;
void processToken(Token token) {
// enumerate objects, perform cryptographic operations, ...
}
ReaderEvents waitTokenListener = new ReaderEvents() {
@Override
public void onReaderAdded(final Reader reader) {
try {
if (token == null && reader.isCardPresent()) {
token = reader.connect();
// cancel event listener
env.removeReaderEventListener(waitTokenListener);
// process token
runOnUiThread(new Runnable() {
@Override
public void run() {
processToken(token);
}
});
}
} catch (SCMException e) {
e.printStackTrace();
}
}
@Override
public void onReaderStateChanged(final Reader reader) {
try {
if (token == null && reader.isCardPresent()) {
token = reader.connect();
// cancel event listener
env.removeReaderEventListener(waitTokenListener);
// process token
runOnUiThread(new Runnable() {
@Override
public void run() {
processToken(token);
}
});
}
} catch (SCMException e) {
e.printStackTrace();
}
}
@Override
public void onReaderRemoved(Reader reader) {
}
@Override
public void onWaitForTokenUserDismissed() {
}
};
AsyncTask<Void, Void, Void> getTokenTask = new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... voids) {
// enumerate reader, check cards already present
try {
List<Reader> listRead = new ArrayList(env.getReaders());
for (Reader r : listRead) {
if (r.isCardPresent()) {
token = r.connect();
runOnUiThread(new Runnable() {
@Override
public void run() {
processToken(token);
}
});
return null;
}
}
}
catch (SCMException e) {
e.printStackTrace();
}
// register event listener
env.addReaderEventListener(waitTokenListener);
return null;
}
};
// ...
getTokenTask.execute();
Token processing¶
Once a Token
is connected, TokenObject
instances contained in the smart card can be accessed. These objects
can have one of 3 following types: Certificate
, PublicKey
and PrivateKey
.
A test on the class of the TokenObject
using instanceof
can be used to identify the kind of object.
Related keys and certificate share the same CkId
identifier.
Note that some token devices may be rather slow, and the initial connection to a token can take up to 5 seconds to complete. Subsequent connections to the same token are faster thanks to Idopte cache.