====== cVEND NFC Reader ====== cVEND is the NFC reader on the bottom half of the PM3 {{ :cvend_plug.pdf |}} The associated serial device appears to be at ''%%/dev/ttymxc3%%'' in Linux. ===== cVEND protocol notes ===== Boot-up / initialization logic analyzer trace (open with the Saleae app): {{ :cvend-bootup.sal |}} ==== IPP packet structure ==== ^ offset ^ length ^ name ^ description ^ | 0 | 1 | | always 0xBC | | 1 | 1 | ''seq'' | monotonically increasing from 1; skips 0 when rolling over | | 2 | 1 | ''flags'' | unknown, usually 0 | | 3 | 1 | ''msgType'' | | | 4 | 2 | ''msgLen'' | length of ''msgData'' | | 6 | 1 | ''hdrCRC'' | CRC-8-Dallas/Maxim over previous 6 bytes | | 7 | ''msgLen'' | ''msgData'' | message payload, structure depends on ''msgType'' | | 7+''msgLen'' | 4 | ''msgCRC'' | little endian inverted IEEE CRC-32 over ''msgData'', iff ''msgType & 0x80'' | ==== IPP message types ==== ^ msgType ^ dir ^ name ^ description ^ | 0x02 | -> | Version | | | 0x03 | <- | VersionReply | (device ID from rockchip otp, firmware version, cwd) as length-prefixed strings\\ e.g. "\x18\x00\xdd\xf5\x14cS01.05.46-49.00-2-2\x0502.35\n/home/app0" | | 0x04 | -> | Status | | | 0x05 | <- | StatusReply | e.g. "\x00\x00" | | 0x07 | <- | Heartbeat| sent periodically by reader | | 0x0f | <- | Startup | sent by reader after startup, approx 1 minute after power on, e.g. "\x00" | | 0x10 | -> | Reset | | | 0x11 | <- | ResetReply | | | 0x20 | -> | LEDs | sets LED status, u32 bitmap; the only two externally visible LEDs on the PM3 are "\x00\x00\x00\x40" and "\x00\x00\x00\x80" | | 0x22 | -> | Buzzer | makes the cvend beep; u16 frequency, u16 duration. e.g. "\x06\x00\x01\x00" | | 0x32 | -> | CardRelease | // registered in ProxCardCtrl::ProxCardCtrl(RFIDReader&)// | | 0x46 | -> | AbortCardHandling | //registered in ProxCardCtrl::ProxCardCtrl(RFIDReader&)// | | 0x96 | -> | PutFile | | | 0x97 | <- | PutFileReply | | | 0x98 | -> | GetFile | | | 0x99 | <- | GetFileReply | | | 0x9a | -> | DeleteFile | | | 0x9b | <- | DeleteFileReply | | | 0x9c | -> | FileInfo | | | 0x9d | <- | FileInfoReply | | | 0xa4 | -> | FileList | | | 0xa5 | <- | FileListReply | | | 0xaa | -> | SetTime | | | 0xab | <- | SetTimeReply | | | 0xac | -> | ITSOData | //conditionally registered in ItsoIppHandler::_handleItsoCtrlReq(CommBuffer&, IppHeader const&, unsigned char const*)// | | 0xad | <- | ITSODataReply | | | 0xae | -> | ITSOCtrl | //registered in ItsoIppHandler::ItsoIppHandler(ProxCardItso&)// | | 0xaf | <- | ITSOCtrlReply | | | 0xb1 | <- | ISORead | sent by reader when ISO14443A card presented, after enabling Iso with 0xe4\\ card UID at offset 2 | | 0xb3 | <- | ISOCardReleased | sent by reader after ISO1443A card released with 0x32 or 0x46 | | 0xb4 | -> | APDUProx | CLA, INS, P1, P2, Lc (2 bytes), Data, Le (2 bytes), and 2 other mystery bytes, in some order | | 0xb5 | <- | APDUProxReply | | | 0xb6 | -> | SAMCtrl | //registered in SamCtrl::SamCtrl()// | | 0xb7 | <- | SAMCtrlReply | | | 0xb9 | <- | DESFireRead | sent by reader when DESFire card presented, after enabling DESFire with 0xe4 | | 0xba | -> | | //unknown, registered in ProxCardDesfire::ProxCardDesfire(RFIDReader&)// | | 0xbb | <- | DESFireCardRemoved | sent by reader when DESFire card removed from field | | 0xbc | -> | DESFireCommand | sends desfire command, documented in {{ 0:m075031_desfire.pdf}} | | 0xbd | <- | DESFireCommandReply | response to command, documented above | | 0xbe | <- | UnhandledCard | sent by reader when a card is presented that is not supported by any enabled ProxCardFunction, containing UID, historical bytes, and other data | | 0xce | -> | | //unknown, registered in IppHandling::IppHandling()// | | 0xd0 | -> | EMV | first byte selects subcommand (0 = load config, 1 = preprocess, 2 = toggle polling) | | 0xd1 | <- | EMVStatus | sent by reader after startup and certain nfc state changes, format and semantics not yet understood | | 0xd4 | -> | | //unknown, registered in ProxCardUltralightC::ProxCardUltralightC(RFIDReader&)// | | 0xe4 | -> | ProxCardFunction | first 2 bytes specify function (4=VdvKa, 5=MifareClassic, 6=Iso, 7=Desfire, 8=Girogo, 9=Itso, 10=UltralightC), next byte must be 1, next byte (0=disable, 1=enable)\\ ''00070101'' sent by PM3 to enable DESFire reading at startup | | 0xe5 | <- | ProxCardFunctionReply | | | 0xe6 | -> | | //unknown, registered in GirogoIppHandler::GirogoIppHandler(RFIDReader&, ProxCardGirogo*)// | | 0xe8 | -> | SecurityServices | first byte selects subcommand (0=GetVersionOfKey, 1=RemoveX509Cert, 2=RemoveKeyBlock, 3=ImportX509Cert, 4=ExportX509Cert, 5=ImportKeyBlock, 6=ExportKeyBlock, 7=GenerateKSKPair), remaining bytes unknown | | 0xe9 | <- | SecurityServicesReply | | | 0xea | -> | | //unknown, registered in ProxCardMifareClassic::ProxCardMifareClassic(RFIDReader&)// | | 0xed | <- | Log | first byte specifies log level (1=INFO, 2=WARNING, 3=ERROR) followed by null-terminated human-readable log message | -> - Host to Reader\\ <- - Reader to Host ==== Card reader flow ==== - Host enables desired card type with ''ProxCardFunction''. - Reader acknowledges with ''ProxCardFunctionReply''. - Reader waits for card, will go to sleep without any stimuli. Can reawaken it with any host command (e.g. ''Status''. - When card is scanned, reader sends the corresponding read packet if the card type is enabled (e.g. ''DESFireRead'' for DESFire). If type is not enabled, sends ''UnhandledCard''. - Card data can then be queried by sending the equivalent command packets. DESFire commands are documented in {{ 0:m075031_desfire.pdf }}. Example for reading a page off a DESFire ( all of the following packets are of type ''DESFireCommand'' and ''DESFireCommandReply''): - Host sets DESFire application (e.g. packet type ''DESFireCommand'' with body ''0x5AF210E0'' for application ID ''0xE010F2''). **NOTE:** The application ID will vary between issuer/agency. For example, the stock software uses application ID ''F9C32B'' while Portland's TriMet uses the one in the example. You can check this for you card with an NFC reader or app. - Reader responds with a status code in accordance with the documentation. This comes in the form of a ''DESFireCommandReply'' packet. - Host sends read command. To read the full contents of file 0x00, the body is ''0xBD00000000000000''. - Reader responds with the status code and file data. ==== Reader -> Host ==== packet type 0xED seems to be a log message: \xBC\xB9\0\xED\0h\xB6\x0306:48:13 Error: feclr_transceive() failed: error: 0, status: 0x00000008 0xB9 - card read? ==== Host -> reader ==== 0xBC - appears after every read (ack of some sort?) ==== Card read flow ==== R: BCB600B9000BBB0C000000048A6EF29B149079CB8CF0 H: BC2200BC000158602A714F60 # DESFire GetVersion (60) R: BCB722BD001E3D00000401013300160504010103001605048A6EF29B149020487330305022C98C7E9B H: BC2300BC0004AA5A2BC3F911A8A56A # DESFire SelectApplication (5A 2BC3F9) R: BCB823BD0002E301FDE0523164 Another one (same payloads as previous but with different ''seq''/''hdrCRC''): R: BCBF00B9000B480C000000048A6EF29B149079CB8CF0 H: BC2400BC0001C4602A714F60 R: BCC024BD001E0800000401013300160504010103001605048A6EF29B149020487330305022C98C7E9B H: BC2500BC0004365A2BC3F911A8A56A R: BCC125BD00027401FDE0523164 Sample log message: BCB000ED0068450330363A34383A3038204572726F723A206665636C725F7472616E7363656976652829206661696C65643A206572726F723A20302C207374617475733A2030783030303030303038200A205B524649445265616465724D756C74694170702E6370703A3135395D0002CD1533BCB100ED0046B40130363A34383A3038207472616E7363656976652065 00000000 bc b0 00 ed 00 68 45 03 30 36 3a 34 38 3a 30 38 |¼°.í.hE.06:48:08| 00000010 20 45 72 72 6f 72 3a 20 66 65 63 6c 72 5f 74 72 | Error: feclr_tr| 00000020 61 6e 73 63 65 69 76 65 28 29 20 66 61 69 6c 65 |ansceive() faile| 00000030 64 3a 20 65 72 72 6f 72 3a 20 30 2c 20 73 74 61 |d: error: 0, sta| 00000040 74 75 73 3a 20 30 78 30 30 30 30 30 30 30 38 20 |tus: 0x00000008 | 00000050 0a 20 5b 52 46 49 44 52 65 61 64 65 72 4d 75 6c |. [RFIDReaderMul| 00000060 74 69 41 70 70 2e 63 70 70 3a 31 35 39 5d 00 02 |tiApp.cpp:159]..| 00000070 cd 15 33 bc b1 00 ed 00 46 b4 01 30 36 3a 34 38 |Í.3¼±.í.F´.06:48| 00000080 3a 30 38 20 74 72 61 6e 73 63 65 69 76 65 20 65 |:08 transceive e| There are 4 bytes appended at the end of some messages (presumably those with ID LSB=1, if the Rust struct description is accurate?) which is not a CRC32 with any polynomial I recognize, but a CRC32 of (message + CRC) is always 0xFFFFFFFF. These 4 bytes are NOT included in the length. The checksum is _appended_, as can be seen by it following the log message (after the final \0). is it a _little endian_, _bit negated_ CRC32. Which is weird because the length is big endian?.. * Host only sends two types of messages after initialization - both of type 0xBC. One is length 1 and the body is always 0x60 (+ negated CRC 2A714F60), the other is of length 4 and the body is always 5A2BC3F9 (+ negated-CRC 11A8A56A)