Skip to content

JSON Remote EMV Terminal

Martin Paljak edited this page Jan 22, 2016 · 45 revisions

RemoteTerminal JSON protocol v0.5.2

A RemoteTerminal represents something akin to an EMV terminal: it is an abstract device that has a smart card (either contact or contactless) reader capable of exchanging APDU-s, (secure) PIN entry capability, a display and a few extra buttons in addition to the pinpad (most commonly green/yellow/red with functionality as described in EMV book 4 chapter 7). Possible client implementations include (but are not limited to): a command line client (included with apdu4j), a GUI client or in the future maybe even a modified actual physical EMV terminal.

RemoteTerminal is built on the RemoteTerminal JSON protocol (specified below), that is not unlike JSON-RPC in nature and builds upon the CardTerminal abstraction as found in Java. The protocol is a series of messages (usually requests-responses pairs) sent over any medium.

Intended use of the RemoteTerminal is centralized solutions (think: card personalization). For a sample server, have a look at esteid.org

For Java, a few interesting classes are provided:

  • RemoteTerminal - implementation of the protocol in Java, as seen from the server side
    • JSONCardTerminal - a remotely proxied CardTerminal that can be used as any other CardTerminal in Java apps(RemoteTerminal.getCardTerminal())
  • SocketTransport - a simple JSON message transport built on (SSL)Socket-s
  • RemoteTerminalServer - a threaded HTTP server that implements JSON messaging.
  • RemoteTerminalThread - extend this Runnable class to implement a synchronous session with the remote terminal
  • CmdlineRemoteTerminalClient - a commandline implementation of the protocol that proxies a connected smart card reader to the server and provides simple UI

For Python, a small client is provided with pyscard and curses support:

  • remote-terminal.py

Sample use from server side (HTTP transport).

class Worker extends RemoteTerminalThread {  
  @Override
  void run() {
    terminal.message("Welcome!");
    CardTerminal ct = terminal.getCardTerminal();
    Card c = ct.connect("*");
    ResponseAPDU r = c.getBasicChannel().transmit(new CommandAPDU(hex2bin("01020304")));
    t.message("Bye!");
  }
}

RemoteTerminalServer server = new RemoteTerminalServer(Worker.class);
server.start(new InetSocketAddress("localhost", 10000));

The HTTP server explicitly does not do HTTPS. It can be deployed behind any common SSL offload box or simple reverse proxy, like the following example with nginx:

upstream backend {
  server 127.0.0.1:10000;
  keepalive 2;
}

server {
  location /update/ {
    proxy_pass http://backend;
  }
}

Or simpler SocketTransport:

Socket s = server.accept();
RemoteTerminal t = new RemoteTerminal(new SocketTransport(s));
t.message("Welcome!");
CardTerminal ct = t.getCardTerminal();
Card c = ct.connect("*"); 
ResponseAPDU r = c.getBasicChannel().transmit(new CommandAPDU(hex2bin("01020304")));
t.message("Bye!");

Sample use from client side

Sample use from client side (CmdlineRemoteTerminalClient):

$ java -jar apdu4j.jar -connect esteid.org:7777 -v
# Welcome!
SCardConnect("A Card Reader Name 01 00", T=*) -> T=1, 3B0000FF00FF
SCardBeginTransaction("A Card Reader Name 01 00")
A>> T=1 (4+000) 01020304
A<< (0000+2) (90ms) 9000
# Bye!
$ java -jar apdu4j.jar -connect https://esteid.org/update/ -v
# Welcome!
SCardConnect("A Card Reader Name 01 00", T=*) -> T=1, 3B0000FF00FF
SCardBeginTransaction("A Card Reader Name 01 00")
A>> T=1 (4+000) 01020304
A<< (0000+2) (90ms) 9000
# Bye!

SocketTransport

Based on the NativeMessaging protocol from Chrome (but uses big endian uint32 as is common in network protocols) Messages bigger than 1024 bytes are rejected by this implementation and the socket is closed.

JSON protocol

General rules

  • Both client and server send messages. Some messages are commands. Only server sends commands.
  • After successfully connecting on the transport layer, a client MUST send a START message, that MAY contain useful information (like client language, operating system etc) that SHOULD be used for customizing the experience.
  • Any party MAY close the underlying transport connection at any time but SHOULD inform the other side by sending a STOP message beforehand
    • Any command may get a STOP response
  • Every message except START and STOP must have a response
  • Command messages have cmd field that contains the command verb in all CAPS.
  • The unique session field, which MUST be present in all messages from the server MUST be echoed back to the server
  • Successful response is <command>:"OK" with additional fields as specified below
  • Failure is indicated with <command>:"NOK" with additional information in ERROR field as a string

Message: START (client->server)

  • NB! This message has no response!
  • {"cmd":"START","lang":"et","platform":"x86_64 GNU/Linux"}

Message: STOP (client->server / server->client)

  • NB! This message has no response!
  • NB! This may be the response to ANY message from either client or server!
  • {"cmd":"STOP","text":"Sudden shutdown"}

Command: CARD

  • Requests a card entry from the terminal, if not yet present (CardTerminal#waitForCardPresent()). This might display a relevant message to the user on the client side.
  • {"cmd":"CARD"}
  • {"CARD":"OK"}

Command: CONNECT

  • Connects to a card (CardTerminal#connect()). Note that actual connection to the card might have happened earlier, so be sure to check the returned protocol for actual value.
  • {"cmd":"CONNECT", "protocol":"*"} where protocol (optional) is one of T=0, T=1, * (T=1 or T=0).
  • {"CONNECT":"OK","atr":"XX..YY","protocol":"T=0","reader":"Some Name"}

Command: DISCONNECT

  • Disconnects from a card (Card#disconnect())
  • Action can be one of leave, reset or eject, with reset being the assumed default. Actual action might not be taken by the client and MAY be rejected.
  • {"cmd":"DISCONNECT", "action":"leave"}
  • {"DISCONNECT":"OK"}

Command: APDU

  • Trancieves an APDU command with the connected card (CardChannel#transmit())
  • {"cmd":"APDU", "bytes":"XX..YY"}
  • OK: {"APDU":"OK","bytes":"XX..YY"}
  • NOK: {"APDU":"NOK","ERROR":"SCARD_E_SOMETHING"} where ERROR is a description of what went wrong. Could be a SCard API error (numeric or symbolic), Java exception message or something similar. Not supposed to be parsed.

Command: MESSAGE

  • Displays a message to the user
  • {"cmd":"MESSAGE", "text":"Hello World!"}
  • {"MESSAGE":"OK"}

Command: DIALOG

  • Displays a dialog message to the user and responds with the pressed button
  • Button can be one of green, red, yellow
  • {"cmd":"DIALOG", "text":"Do you want to continue?"}
  • {"DIALOG":"OK","button":"green"}

Command: VERIFY

  • Verifies a PIN code with ISO VERIFY. When secure PIN entry is used, the client is assumed to be aware of the right PIN format. Client MAY interpret the response to the VERIFY command to re-ask for the PIN entry or similar.
  • {"cmd":"VERIFY","p2":1,"text":"Do you authenticate with PIN1?"}
  • OK: {"VERIFY":"OK"}
  • NOK: {"VERIFY":"NOK","bytes":"63C2"} where bytes is the result (SW) of the VERIFY command. Can also contain "button":"red"

Command: DECRYPT

  • Special form of APDU command, that shows the (UTF-8 stringified) response from the card to the user instead of returning it to the server. The user must confirm the plaintext.
  • {"cmd":"DECRYPT","bytes":"XX..YY","text":"Please note down the sensitive information"}
  • OK: {"DECRYPT":"OK","button":"green"}

See also