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:
- Server part connects to the cards and provides API via REST and WebSocket interface.
- Client part is a library for connecting to the various card sources, e.g., physical, jcardsim, remote server.
- GP is a GlobalPlatformPro wrapper with remote card channel added, so you can manipulate with the cards, e.g., install applets remotely.
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.
Remote-card has support for VSmartCard in two modes:
- CardManager can connect to existing VICC instances. Usage: Use remote reader, remotely accessed card via VICC.
- VSmartCardWrapper provides any card under VICC interface. Usage: Local VPCD can connect to the VSmartCardWrapper and communicate with the card via CardManager (e.g., remote card accessed via REST interface).
Other sources:
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:
- Locally connected physical card
- Locally setup JCardSim
- Remote card access via server component. The remote card on the server-side can be again physical or emulated.
- VSmartCart, acts as VPCD, communicates with VICC (or with remote-reader).
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 takes own parameters like
--remote-card
contains remote address endpoint--remote-reader-idx
defines reader index on the server to use, defaults to 0--remote-type
defines card type to use on the remote server, defaults tocard
. Note that JCardSim does not support GP commands now
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"
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.
- Install remote-reader on your Android phone.
- Or you can build it from source
- It is preferred to use updated version updated version, which supports listening mode.
- Configure the remote-reader to connect to your PC (specify IP address in configuration)
- Start GP wrapper in listening mode. It starts a local server on 0.0.0.0 listening on the default port 35963.
- Tap the card to the Android. On tap, connection to the PC running GP wrapper is created.
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"
Visit this page on iOS remote-card
Server part below.
./gradlew :server:run --args='--help'
./gradlew :server:run --args='--reader-idx=0 --allow-pick-reader'
Not currently supported, may be added later:
- Configuring from JSON configuration file.
- Add authentication to the clients, via access tokens.
- Add reader index allocation and locking policy via reservation tokens.
- Enable TLS on the HTTP server. Vertx has already support for it, it is just needed to configure it.
/v1/ping
is a simple ping endpoint, just to test if server responds/v1/card
is endpoint responsible for all card operations
API can be called in two ways:
- GET request with
req
parameter containing JSON request - POST request with
application/json
type, body contains serialized JSON request
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:
/v1/card/card/0/is_connected
/v1/card/card/0/connect
/v1/card/card/0/disconnect
/v1/card/card/0/atr
/v1/card/card/0/select/001020303040506
/v1/card/card/0/cmd/0001000000
/v1/card/sim/0/is_connected
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).
{"action": "connect", "target": "card", "idx": 0}
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.
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}
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}
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.
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}