National Railways Federation

Branch federation of the railway operating cooperatives

Dubrazec standard, revision 3, wire protocol

The layout of the card, its memories and its electrical figures are on the CARD-ROM page. This document is the protocol: frame format, the twelve commands in their encoding, the authentication sequence, how a record is sealed, and the status words a reader must handle.

Frames

Anticollision and the half-duplex block protocol are ISO 14443-3 Type A, unchanged. Above them the standard defines its own frame rather than ISO 14443-4, because the deadline at a gate does not survive the block chaining negotiation. A frame is at most 264 bytes over the coil and 1,032 over the pads.

Frame layout
Offset Length Field Note
0 1 CMD Command code, 0x01 to 0x0C.
1 1 FLG Bit 0 session open, bit 1 contact interface, bit 2 more frames follow, bits 3 to 7 zero.
2 2 LEN Payload length, big endian.
4 LEN Payload Command arguments, or the reply.
4+LEN 2 SW Status word. Present in replies only.
6+LEN 2 CRC CRC-16 as in ISO 14443-3, over the whole frame.

Commands

The twelve commands
Code Command Payload out Payload back Typical
0x01 SELECT none revision, region sizes, capability bits 12 ms
0x02 READ_ID none 256 B ROM block 28 ms
0x03 AUTH key ref, reader nonce, 8 B card nonce and MAC, 24 B 46 ms
0x04 READ_OPT offset, length bytes 9 ms
0x05 WRITE_OPT offset, bytes none 22 ms
0x06 READ_REC from index, count, max 64 records, 32 B each 18 ms
0x07 APPEND_REC record, 32 B index written 62 ms
0x08 MERGE source UUID, session token count copied 340 ms
0x09 EXPIRE cut-off stamp count dropped 38 ms
0x0A STATUS none wear counters, bad blocks, record count 11 ms
0x0B READ_FREE offset, length bytes 14 ms
0x0C WRITE_FREE offset, bytes none 26 ms

WRITE_OPT, WRITE_FREE and MERGE return 0x6982 over the coil in every build. A card in a pocket cannot be written to by a reader held near it.

Authentication

reader -> card   SELECT
card   -> reader  rev=04 rom=0100 eep=2000 nand=200000 caps=0x0F

reader -> card   READ_ID
card   -> reader  uuid=04:9C:2E:7B:41:80:00 issuer=... sig=Ed25519(...)
                 reader checks sig against the issuer key it holds

                 K = AES-128-CMAC(Kmaster, uuid || 0x01)   per-card key

reader -> card   AUTH keyref=00 rndR=8B
card   -> reader  rndC=8B  macC=AES-CMAC(K, rndR || rndC)[0..7]
reader -> card   AUTH keyref=00 macR=AES-CMAC(K, rndC || rndR)[0..7]
card   -> reader  SW=9000   session open, SK = AES-CMAC(K, rndR || rndC)

The session key seals every frame that follows until the field drops or 0x0F is sent. A reader that fails the second pass gets 0x6300 and three attempts before the card refuses the key reference for 200 ms, which is long enough to make a search pointless at a gate and short enough that a person who taps twice notices nothing.

Records

32 bytes, big endian throughout
0   u32  stamp      minutes since 2000-01-01 00:00 local standard time
4   u64  station    station identifier, see /v1/stations
12  u64  section    pair of station identifiers, 32 bits each, or zero
20  u8   count      1 to 9
21  u8   kind       0 gate 1 machine 2 aboard 3 merge 4 issue
22  u16  operator   cooperative identifier
24  u64  seal       AES-CMAC(Knet, bytes 0..23 || uuid)[0..7]

The seal is taken under the network key, not the per-card key, so a record written in Iosmein is checkable by a Fluid gate that holds the Iosmein network key and by nothing else. A gate that cannot check a seal counts the record and marks it unverified in its own log; it never refuses passage over it.

A record with kind 3 puts the UUID of the card it came from in the section field, and the count field says how many records followed it.

Status words

Status words, revision 3
SW Meaning What a gate does
0x9000 Done. Continues.
0x6300 Authentication failed. Retries once, then shows the panel that names the card and refuses passage.
0x6581 NAND write failed after retries. Opens anyway and logs the card as unwritable, so the counter can replace it.
0x6700 Bad length. Firmware fault. Logged and reported.
0x6982 Refused on this interface. Only over the coil, for the three write commands.
0x6A82 Region not present. Card of an older revision. Falls back to READ_ID and the count is taken without a section.
0x6A84 No space, all blocks worn. Opens, and the panel asks the holder to take a new card at the machine.
0x6F00 Card lost the field mid-command. Nothing is committed. The journal replays on the next presentation.

Test vectors for diversification, both authentication passes and the record seal are in vectors.txt here, and in the tree at source.nrf.rof/conformance/vectors. A build that reproduces them is a build the conformance suite will run against.