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 a android: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 in app/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 directly SCMActivity 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);
        [...]
    }
}
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.