This repository contains the source files that are used in the server, MitM-Proxy, and main application. They have to be compiled using Google Protocol Buffers, version 2.6.1 (NOT 3.X). The compiled files are already shipped within the respective repositories, so you only need to touch these files if you want to modify the protocol.
The protocol offers a few different functions, each with their own protocol messages. In this document, we'll try to give an overview of how the protocol works. Again, you only need to know this if you want to change it or implement your own clients. The message types are split into three different protobuf files:
c2s.proto
contains messages passed between client and server. This is mostly related to session management and message passingc2c.proto
contains messages passed between two clients. This includes the meat of the protocol: Passing NFC data and status informationmetaMessage.proto
contains only one data type: AWrapper
message that is used to identify the type of message that is passed
Wrapper
messages are only required because we can only parse the transferred bytes into Protobuf messages if we know the type of message to parse it into. For this purpose, we introduced the Wrapper
message, which contains exactly one message any other message type. That way, we can parse any incoming message as a Wrapper
message and then use the functions the parsed message offers to identify which type of message is contained in it.
Developers: If you want to send any message, you MUST wrap it in a Wrapper
message. If you want to add new message types, you need to add them to this message type as well (and obviously add support for it in all clients that need to understand it).
Clients connect using sessions. Each session can have one or two participants (although a session with one participant is not very useful and generally only happens while you wait for the second device to connect). Sessions are identified using a 6-digit session token, which is generated by the server.
A session is started by a client sending a Session
Message (defined in c2s.proto
) with an opcode of SESSION_CREATE
and a SessionErrorCode of ERROR_NOERROR
(this should be the default value anyway). As mentioned before, the Session
message MUST be wrapped in a Wrapper
message. It can then be serialized into a byte stirng and sent over the wire to the server. The server will parse the Wrapper
message, see that it contains a Session
message, parse that message, see that it is a session creation request, and then perform the following algorithm (as pseudocode, if errcode is not set, it defaults to ERROR_NOERROR
. Replies are always Session
messages wrapped in a Wrapper
message):
if (client already in session):
reply (opcode=SESSION_CREATE_FAIL, errcode=ERROR_CREATE_ALREADY_HAS_SESSION)
else:
generate random unique 6-digit token
create session identified by that token
add client to that session
reply (opcode=SESSION_CREATE_SUCCESS, session_secret=token)
Clients can join a session by sending a Session
message with an opcode of SESSION_JOIN
, errcode ERROR_NOERROR
, and session_secret set to the session token. The message is wrapped in a Wrapper
message and passed to the server. The server then performs the following algorithm (pseudocode, if errcode is not defined, it is set to ERROR_NOERROR
, replies are always Session
messages wrapped in a Wrapper
message):
if (token not in database):
reply (opcode=SESSION_JOIN_FAIL, errcode=ERROR_JOIN_UNKNOWN_SECRET)
else if (session already has two participants):
reply (opcode=SESSION_JOIN_FAIL, errcode=ERROR_JOIN_SESSION_FULL)
else if (client is already in a session):
reply (opcode=SESSION_JOIN_FAIL, errcode=ERROR_JOIN_ALREADY_HAS_SESSION)
else:
Add client to session
reply (opcode=SESSION_JOIN_SUCCESS)
notify other client that client has joined session (opcode=SESSION_PEER_JOINED)
Clients can also leave a session by sending a Session
message with opcode SESSION_LEAVE
and session_secret set to the session token. The server will perform the following algorithm:
if (client not in session identified by session_secret):
reply (opcode=SESSION_LEAVE_FAIL, errcode=ERROR_LEAVE_NOT_JOINED)
else if (no session with that token exists):
reply (opcode=SESSION_LEAVE_FAIL, errcode=ERROR_LEAVE_NOT_JOINED)
else:
remove client from session
reply (opcode=SESSION_LEAVE_SUCCESS)
if (session empty):
remove session from database
else:
notify other client (opcode=SESSION_PEER_LEFT)
This concludes the session management part of the protocol.
Once two clients are in a session, they can pass messages to each other. The process may seem overly complicated at first glance, but there is a reason for everything, and they will be explained over the course of this documentation.
Data
messages are a defined in c2s.proto
. They are used to wrap messages between clients in a way the server can understand. They contain an error code, which is used by the server to communicate forwarding errors if they occur, and a raw byte array, which contains the byte-encoded message that is passed. The data message is, as always, wrapped in a Wrapper
message, and the byte-encoded message is always a serialized Wrapper
message. So, sending any message between clients involves the following stack:
Create message that should be passed to other client (discussed later)
Wrap in Wrapper message
Encode as byte array
Put byte array into data message
Wrap data message in Wrapper message
Send to server
The reasons such a complicated scheme was chosen is encryption. Originally, we planned to perform end-to-end-encryption on all messages. In that case, a way to pass arbitrary byte strings between clients was required, so the Data
message was born. Due to time constraints, the E2E encryption was dropped from the plans for version 1.0, but the code was already built using Data
messages. As we are still planning on adding encryption eventually, we decided not to rewrite everything, only to re-write it back once encryption is added, and instead stuck with the Data
message. For the same reason, the byte array contained in the data message must contain a Wrapper
message: We need to be able to parse it into a known message type, before being able to identify the type of message that was actually sent.
If the server receives a wrapped Data
message, it performs the following algorithm (replies are Data
messages with an empty byte blob):
if (client is not in a session):
reply (errcode=ERROR_NO_SESSION)
else if (no second client in session):
reply (errcode=ERROR_NO_SESSION)
else:
try to forward message to other client
if (error occured during forwarding):
reply (errcode=ERROR_TRANSMISSION_FAILED)
else:
reply (errcode=ERROR_NOERROR)
Now that we know how messages are passed between clients, let's take a look at the messages that can be passed. All of the following messages are passed in the same way: Create message, wrap in Wrapper
, serialize to byte string, create Data
message with byte array, pack in Wrapper
, serialize, send to server.
Anticol
messages (defined in c2c.proto
) contain the information from the anticollision loop of the NFC protocol. This includes UID, ATQA, SAK and Historical byte. The messages are populated with the information and sent using Data
messages as described above. Is sent as soon as a client is connected to a new NFC Card.
NFCData
messages (also defined in c2c.proto
) contain actual NFC APDUs, encoded as byte strings. Additionally, they contain some information about the source of the APDU (i.e. is it a message from an NFC reader or a reply by the card), encoded in the data_source field.
The Status
message type (defined in c2c.proto) is used to convey information about the status of a client. The following types are defined:
KEEPALIVE_REQ
: Keepalive message, used to test if the connection is still alive. MUST be answered with aKEEPALIVE_REP
KEEPALIVE_REP
: Reply toKEEPALIVE_REQ
CARD_FOUND
: Sent by a client that is in proximity to a Card. Will be followed by anAnticol
message encoding information about the card.CARD_REMOVED
: Sent by a client if a previously detected card has left proximityREADER_FOUND
: Sent if a client is in proximity to a readerREADER_REMOVED
: Sent if the client leaves proximity to a previously discovered readerNFC_NO_CONN
: Sent in response to aNFCData
message if the receiving client is not connected to any NFC device and thus cannot act upon the NFCData message.INVALID_MSG_FMT
: Sent if a client does not understand a received message. Could indicate that it is a legacy client that does not support new protocol messages, or that a message was corrupted.NOT_IMPLEMENTED
: Sent if a client understands a received message, but has not implemented the requested functionality. Mostly used during development if a function is only implemented as a stub, should not occur in production.UNKNOWN_MESSAGE
: Sent if a Wrapper message is received, but the encoded message cannot be understood. Similar toINVALID_MSG_FMT
.UNKNOWN_ERROR
: Catchall status code if something went wrong, but the client is not quite sure what.
It is the responsibility of the clients to act upon these status messages in a sensible way.