Home

Awesome

JavaCard remote access

Wraps physically connected JavaCards and virtual JCardSim cards behind REST and WebSocket interface. Enables to connect multiple JavaCards on one host and access them remotely on a different host. Project also integrates with VSmartCard so Android NFC phone can be used as a card reader.

The project contains 3 main parts:

Usage diagram

Project currently does not integrate wit PCSC natively, so the remote card is not transparently installed as a card reader. This may be added in future releases. Project provides JVM-only, self-contained remote card access solution (no need to install drivers or compile anything). For low-level solution with local reader emulation check out virtualsmartcard.

This project can be also used when testing JavaCard mobile applications. Implement abstract card channel on the mobile platform that has two options - real card and remote card. Using remote card in initial phases of the development can speed up the development process. Also, the remote card can be JcardSim based card, thus you can debug (e.g., put breakpoint) your applet code during mobile app development.

Tool was created to support remote learning in CRoCS, 1. Support for remote cards is already integrated in JavaCard gradle template project.

VSmartCard

Remote-card has support for VSmartCard in two modes:

Other sources:

Client

Client library provides CardManager class that enables to connect to various cards under unified API. If the app uses CardManager to create a card connection and communicate with the card, application can switch between card backends transparently.

Card sources:

Example of creating a connection to a remote card:

val cfg = RunConfig.getDefaultConfig().apply {
    testCardType = CardType.REMOTE              // type of the card to connect to
    remoteAddress = "http://127.0.0.1:9001"     // remote card REST endpoint
    targetReaderIndex = 0                       // remote card reader index 0
    remoteCardType = CardType.PHYSICAL          // remote card is physical
}

val mgr = CardManager(true, null)
mgr.connect(cfg)
val apduResp = mgr.transmit(CommandAPDU(0, 0x4a, 4, 0, Hex.decodeHex("0102030405")))

// Obtain javax.smartcardio.CardChannel
val channel = mgr.channel

// Obtain javax.smartcardio.Card
val card = mgr.channel.card

Client is accessible via Maven repository: https://mvnrepository.com/artifact/com.klinec/javacard-tools

implementation 'com.klinec:javacard-tools:1.0.4'

GP wrapper

GP wrapper takes own parameters like

Always specify there arguments when using the wrapper first, then other arguments to GP follow. If you have difficulty specifying GP arguments, separate wrapper and GP arguments by --.

If --remote-card is not specified, locally connected card is used instead, obeying GP_READER env var.

Usage listing installed applets on a remote card:

./gradlew :gp:run --args="--remote-card=http://127.0.0.1:9901 --list -d"

VSmartCard

GP Wrapper enables to use GP with cards accessible via VICC. In this way you can manage applets on the physical JavaCard via Android smartphone, without need to have a card reader.

Starting GP in listening mode:

./gradlew :gp:run --args="--card-type vsmartcard --listen --list -d"

It works like this:

Physical Card <---> Android <--- tcp:35963 ---> GPWrapper <---> GP

GP Wrapper also supports a listener mode (reversed). You need to provide hostname of the card endpoint:

./gradlew :gp:run --args="--card-type vsmartcard --remote-card 192.168.1.20 --list -d"

iOS version, limitations

Visit this page on iOS remote-card

Server

Server part below.

Server Usage

./gradlew :server:run --args='--help'
./gradlew :server:run --args='--reader-idx=0 --allow-pick-reader'

Future work

Not currently supported, may be added later:

REST API

API can be called in two ways:

Response is JSON encoded, contains result integer field. If there was no error, it contains 0. Other values indicate some problem with processing. Field error can contain additional information about the error (e.g., exception message).

Convenience API endpoints are available according to the scheme:

/v1/card/:card_type/reader_index/action

Example:

API logic - physical cards

All clients connected to the server share connected physical readers. So if multiple clients use the same reader index, probably the exception is thrown due to race condition. Simultaneous access by multiple clients to shared readers is not yet supported. So clients has to cooperatively decide, which card reader to use.

Alternatively, you can start multiple server instances, each running on custom ports, each having different --reader-idx=0 default card reader index specified. You can thus specify different REST endpoints for different clients.

When server is started, no reader probing is performed.

Before sending any commands, client has to ask for card connections. Request for connecting to the physical card, with card index 0 (if --allow-pick-reader is specified, otherwise --reader-idx is used).

Connect

{"action": "connect", "target": "card", "idx": 0}

Is connected

User can test if the connection to the card already exists:

{"action":"is_connected", "target": "card", "idx": 0}

Response:

{"result":0, "num_connections":1, "connected":true, "ctype":"card"}

In this case, field connected indicates the card is already connected and APDU requests can be sent.

Select applet

Before sending APDUs to your applet, you have to select the applet

{"action":"select", "target": "card", "idx": 0, "aid":"02ffff0405060708090103"}

Response:

{"result":0, "num_connections":1, "response":"000000000000000000006C5544797A91115DC3330EBD003851D239A706FF2AA29000", "sw":36864, "sw_hex":"9000", "sw1":144, "sw2":0}

APDU command

And finally, you can send APDU commands:

{"action":"send", "target":"sim", "apdu":"0001000000"}

Response:

{"result":0, "num_connections":1, "response":"0000000000000000000051373E8B6FDEC284DB569204CA13D2CAA23BD1D85DCA9000", "sw":36864, "sw_hex":"9000", "sw1":144, "sw2":0}

API logic - JCardSim

In order to support JCardSim over REST (virtual remote card), one has to add applet code to the project so the simulator can pick it up.

This project adds DemoApplet and DemoApplet2 applets to the {"target":"sim"} simulator. In order to specify your applets for a simulator, override App.configureCard method.

WebSocket API

Request and response payloads are the same. WebSocket is stateful, but you still have to add target and index to all your requests.

Websocat enables CLI interaction with the WebSocket API

websocat ws://127.0.0.1:9901

If request contains rid field, WebSocket API returns rid in the response, so the client can pair requests to the responses.

{"action":"is_connected"}
{"session":"7812ed35-8017-4431-85e7-637ca96634a0","result":0,"num_connections":1,"connected":false,"ctype":"?"}

{"action":"connect","target":"sim"}
{"session":"7812ed35-8017-4431-85e7-637ca96634a0","result":0,"num_connections":1}

{"action":"select","target":"sim","aid":"01ffff0405060708090102"}
{"session":"7812ed35-8017-4431-85e7-637ca96634a0","result":0,"num_connections":1,"response":"B2F3A2D4FBB002D9F0B51258AF43E98A5423FB145257AE460342361C2199D3809000","sw":36864,"sw_hex":"9000","sw1":144,"sw2":0}

{"action":"send","target":"sim","apdu":"0001000000"}
{"session":"7812ed35-8017-4431-85e7-637ca96634a0","result":0,"num_connections":1,"response":"9D4169CC1427F2A407191B84AB7ABAACE66A95CA26AB0915803106315080F3319000","sw":36864,"sw_hex":"9000","sw1":144,"sw2":0}

{"action":"is_connected","target":"sim"}
{"session":"7812ed35-8017-4431-85e7-637ca96634a0","result":0,"num_connections":1,"connected":true,"ctype":"sim"}

{"action":"select","target":"sim","aid":"02ffff0405060708090103"}
{"session":"5ae71460-44b1-44ab-b8f7-ad6987c7faae","result":0,"num_connections":1,"response":"000000000000000000006C5544797A91115DC3330EBD003851D239A706FF2AA29000","sw":36864,"sw_hex":"9000","sw1":144,"sw2":0}

{"action":"send","target":"sim","apdu":"0001000000"}
{"session":"5ae71460-44b1-44ab-b8f7-ad6987c7faae","result":0,"num_connections":1,"response":"0000000000000000000051373E8B6FDEC284DB569204CA13D2CAA23BD1D85DCA9000","sw":36864,"sw_hex":"9000","sw1":144,"sw2":0}