diff --git a/.github/workflows/build_firmware.yml b/.github/workflows/build_firmware.yml index b584580d..8639bcea 100644 --- a/.github/workflows/build_firmware.yml +++ b/.github/workflows/build_firmware.yml @@ -23,6 +23,7 @@ jobs: uses: actions/checkout@v3 with: ref: ${{ inputs.checkout-sha == null && github.sha || inputs.checkout-sha }} + fetch-depth: 0 - name: ghcr.io login uses: docker/login-action@v2 with: @@ -56,6 +57,7 @@ jobs: uses: actions/checkout@v3 with: ref: ${{ inputs.checkout-sha == null && github.sha || inputs.checkout-sha }} + fetch-depth: 0 - name: Build firmware env: repo: ${{ github.repository }} diff --git a/.github/workflows/on_push.yml b/.github/workflows/on_push.yml index d5ef5dea..08c228ed 100644 --- a/.github/workflows/on_push.yml +++ b/.github/workflows/on_push.yml @@ -13,7 +13,7 @@ jobs: client_pipeline: name: Build Firmware uses: ./.github/workflows/build_client.yml - create_release: + create_dev_release: permissions: contents: write name: Create dev pre-release with artifacts @@ -49,3 +49,32 @@ jobs: run: | git tag -f dev git push --tags -f + create_release: + permissions: + contents: write + name: Create tagged release with artifacts + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/v') + needs: + - firmware_pipeline + - client_pipeline + steps: + - name: Check out the repo + uses: actions/checkout@v3 + - name: Download release artifacts + uses: actions/download-artifact@v3 + with: + name: release-artifacts + path: release-artifacts + - name: Upload to tagged release + uses: softprops/action-gh-release@v1 + with: + body: | + Auto-Generated DFU packages for Release ${{ github.ref_name }} + Built from commit ${{ github.sha }} + name: Release ${{ github.ref_name }} + draft: false + target_commitish: ${{ github.sha }} + generate_release_notes: true + append_body: true + files: release-artifacts/* diff --git a/CHANGELOG.md b/CHANGELOG.md index d3cedbcb..76153192 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,34 @@ All notable changes to this project will be documented in this file. This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log... ## [unreleased][unreleased] - - Added `hf settings blepair` command to get and set ble pairing enable state, and default disable ble pair. (@xianglin1998) + +## [v2.0.0][2023-09-26] + - Changed APP_FW_VER now deduced from git tag vx.y.z (@doegox) + - Changed initial button wakeup from 4 to 8 seconds (@aramova) + - Added MIFARE Ultralight reading features (@FlUxIuS & @doegox) + - Fixed MF1 write mode SHADOW was not preserved properly (@doegox) + - Changed field LED now active also in reader mode to indicate that reader is powering the field (@doegox) + - Changed slot enabled logic: now we have separate enabled_hf and enabled_lf, changed GET_ENABLED_SLOTS and SET_SLOT_ENABLE (@doegox) + - Changed tag type enum to be ready for new types, changed stored slotConfig and GET_SLOT_INFO (@doegox) + - Added HF14A_RAW and its support in `hf 14a raw` (@xianglin1998) + - Removed MF1_DETECT_DARKSIDE (@doegox) + - Added MF1_STATIC_NESTED_ACQUIRE and its support in `hf mf nested` (@xianglin1998) + - Changed `hf 14a scan`: Automatically send RATS to 14443-4a tags (@augustozanellato) + - Changed Darkside: use LEDs for visual feedback of attack progression (@doegox) + - Changed Darkside: longer RF field off for reset and longer CLI timeout (@doegox) + - Fixed Darkside: parity byte-to-array bug made it low probability to succeed (@doegox) + - Changed `hw detection decrypt` show progression and remove duplicate keys (@doegox) + - Changed dynamic cmd_map_init() by static cmd_map initialization (@doegox) + - Changed `hf slot list` to add clarity and colors (@doegox) + - Changed `hf mf sim` and `hf mf info` to support ATS (still to be used in actual emulation) (@doegox) + - Changed `hf mf eload` and `hf mf eread`: uploads/downloads are now 30x faster (@doegox) + - Changed CLI HF14AInfo logic merged inside HF14AScan for more consistent display of the results (@doegox) + - Added guessed type information for NXP tags, and reorganization of HF information part. (@FlUxIuS) + - Changed `hw raw` to detail status message (@doegox) + - Changed CLI to query capabilities on connect, not on every single command if device does not support get_device_capabilities (@doegox) + - Changed CLI to not instanciate ChameleonCMD on every single command (@doegox) + - Changed massively the protocol and its handlers for more consistency and easier maintenance and future dev (@doegox) + - Added `hf settings blepair` command to get and set ble pairing enable state, and default disable ble pair (@xianglin1998) - Added `hf mf info` command to get UID/SAK/ATQA from slot (@Foxushka) - Added `hw raw` to send raw command to Chameleon (@Foxushka) - Added command to fetch all available commands from Chameleon and test if Chameleon supports it (@Foxushka) diff --git a/docs/development.md b/docs/development.md index da352db2..409dbd56 100644 --- a/docs/development.md +++ b/docs/development.md @@ -345,3 +345,7 @@ Limitations: * SWO pin is shared with... SWO so when e.g. reflashing the device, garbage may appear on the monitoring terminal. * SWO pin is also shared with the blue channel of the RGB slot LEDs, so faint blue may appear briefly when logs are sent and LED might not work properly when supposed to be blue. + +# Resources + +* [nRF52840 Objective Product Specification v0.5.1](https://infocenter.nordicsemi.com/pdf/nRF52840_OPS_v0.5.1.pdf) diff --git a/docs/images/Makefile b/docs/images/Makefile new file mode 100644 index 00000000..814efe75 --- /dev/null +++ b/docs/images/Makefile @@ -0,0 +1,5 @@ +all: + pdflatex --shell-escape protocol-packet.tex + +clean: + rm *.aux *.log *.pdf diff --git a/docs/images/cli_staticnested.jpg b/docs/images/cli_staticnested.jpg new file mode 100644 index 00000000..05272b46 Binary files /dev/null and b/docs/images/cli_staticnested.jpg differ diff --git a/docs/images/protocol-packet.png b/docs/images/protocol-packet.png index 98372b5e..2092f14f 100644 Binary files a/docs/images/protocol-packet.png and b/docs/images/protocol-packet.png differ diff --git a/docs/images/protocol-packet.tex b/docs/images/protocol-packet.tex new file mode 100644 index 00000000..82449ac0 --- /dev/null +++ b/docs/images/protocol-packet.tex @@ -0,0 +1,24 @@ +\documentclass[border=10pt,png]{standalone} +\usepackage{bytefield} +\usepackage{xcolor} + +\begin{document} + + \definecolor{lightcyan}{rgb}{0.85,1,1} + \definecolor{lightgreen}{rgb}{0.85,1,0.85} + \definecolor{lightred}{rgb}{1,0.85,0.85} + \begin{bytefield}[bitwidth=1.1em]{32} + \bitbox{8}[bgcolor=lightcyan]{SOF} & + \bitbox{8}[bgcolor=lightcyan]{LRC1} & + \bitbox{16}[bgcolor=lightgreen]{CMD} \\ + \bitbox{16}[bgcolor=lightgreen]{STATUS} & + \bitbox{16}[bgcolor=lightgreen]{LEN} \\ + \bitbox{8}[bgcolor=lightgreen]{LRC2} & + \bitbox[tlr]{24}[bgcolor=lightred]{} \\ + \wordbox[lr]{1}[bgcolor=lightred]{DATA} \\ + \wordbox[lr]{1}[bgcolor=lightred]{$\cdots$} \\ + \bitbox[blr]{24}[bgcolor=lightred]{} & + \bitbox{8}[bgcolor=lightred]{LRC3} + \end{bytefield} + +\end{document} diff --git a/docs/protocol.md b/docs/protocol.md index 70f57257..c89c1fde 100644 --- a/docs/protocol.md +++ b/docs/protocol.md @@ -2,28 +2,387 @@ **WIP** -## Packets format +## Frame format -The communication with the application is not the easiest but is structured as follows: +The communication between the firmware and the client is made of frames structured as follows: ![](images/protocol-packet.png) -- **SOF**: `1 Byte`, the "Magic Byte" represent the start of a packet, must be `0x11`. -- **LRC1**: `1 Byte`, the LRC ([**L**ongitudinal **R**edundancy **C**heck](https://en.wikipedia.org/wiki/Longitudinal_redundancy_check)) of the `SOF`, must be `0xEF`. -- **CMD**: `2 Bytes` in unsigned [Big Endian](https://en.wikipedia.org/wiki/Endianness) format, each command have been assigned a unique number (e.g. `factoryReset(1020)`), this is what you are sending to the device. -- **STATUS**: `2 Bytes` in unsigned [Big Endian](https://en.wikipedia.org/wiki/Endianness) format. If the direction is from APP to hardware, the status is always `0x0000`. If the direction is from hardware to APP, the status is the result of the command. -- **LEN**: `2 Bytes` in unsigned [Big Endian](https://en.wikipedia.org/wiki/Endianness) format, the length of the data, maximum is `512`. -- **LRC2**: `1 Byte`, the LRC ([**L**ongitudinal **R**edundancy **C**heck](https://en.wikipedia.org/wiki/Longitudinal_redundancy_check)) of the `CMD`, `STATUS` and `LEN`. -- **DATA**: `LEN Bytes`, the data to send or receive, maximum is `512 Bytes`. This could be anything, for example you should sending key type, block number, and the card keys when reading a block. -- **LRC3**: `1 Byte`, the LRC ([**L**ongitudinal **R**edundancy **C**heck](https://en.wikipedia.org/wiki/Longitudinal_redundancy_check)) of the `DATA`. +- **SOF**: `1 byte`, "**S**tart-**O**f-**F**rame byte" represents the start of a packet, and must be equal to `0x11`. +- **LRC1**: `1 byte`, LRC over `SOF` byte, therefore must be equal to `0xEF`. +- **CMD**: `2 bytes`, each command have been assigned a unique number (e.g. `DATA_CMD_SET_SLOT_TAG_NICK` = `1007`). +- **STATUS**: `2 bytes`. + - From client to firmware, the status is always `0x0000`. + - From firmware to client, the status is the result of the command. +- **LEN**: `2 bytes`, length of the `DATA` field, maximum is `512`. +- **LRC2**: `1 byte`, LRC over `CMD|STATUS|LEN` bytes. +- **DATA**: `LEN bytes`, data to be sent or received, maximum is `512 bytes`. This payload depends on the exact command or response to command being used. See [Packet payloads](#packet-payloads) below. +- **LRC3**: `1 byte`, LRC over `DATA` bytes. -The total length of the packet is `LEN + 10` Bytes. For receiving, it is the exact same format. - -Note: LRC2 and LRC3 can be computed equally as covering either the frame from its first byte or from the byte following the previous LRC, because previous LRC nullifies previous bytes LRC computation. +Notes: +* The same frame format is used for commands and for responses. +* All values are **unsigned** values, and if more than one byte, in **network byte order**, aka [Big Endian](https://en.wikipedia.org/wiki/Endianness) byte order. +* The total length of the packet is `LEN + 10` bytes, therefore it is between `10` and `522` bytes. +* The LRC ([**L**ongitudinal **R**edundancy **C**heck](https://en.wikipedia.org/wiki/Longitudinal_redundancy_check)) is the 8-bit two's-complement value of the sum of all bytes modulo $2^8$. +* LRC2 and LRC3 can be computed equally as covering either the frame from its first byte or from the byte following the previous LRC, because previous LRC nullifies previous bytes LRC computation. E.g. LRC3(DATA) == LRC3(whole frame) -## Packet payloads +## Data payloads Each command and response have their own payload formats. -TODO: +Standard response status is `STATUS_DEVICE_SUCCESS` for general commands, `HF_TAG_OK` for HF commands and `LF_TAG_OK` for LF commands. +See [Guidelines](#new-data-payloads-guidelines-for-developers) for more info. + +* **TODO:** num_to_bytes bytes_to_num +* **FIXME:** mf1_get_emulator_config with bits -> bytes (5) with 4 bools <> mf1_get_detection_log with bitfield (2)... + +Beware, slots in protocol count from 0 to 7 (and from 1 to 8 in the CLI...). + +In the following list, "CLI" refers to one typical CLI command using the described protocol command. But it's not a 1:1 match, there can be other protocol commands used by the CLI command and there can be other CLI commands using the same protocol command... + +### 1000: GET_APP_VERSION +* Command: no data +* Response: 2 bytes: `version_major|version_minor` +* CLI: cf `hw version` +### 1001: CHANGE_DEVICE_MODE +* Command: 1 byte. `0x00`=emulator mode, `0x01`=reader mode +* Response: no data +* CLI: cf `hw mode set` +### 1002: GET_DEVICE_MODE +* Command: no data +* Response: data: 1 byte. `0x00`=emulator mode, `0x01`=reader mode +* CLI: cf `hw mode get` +### 1003: SET_ACTIVE_SLOT +* Command: 1 byte. `slot_number` between 0 and 7 +* Response: no data +* CLI: cf `hw slot change` +### 1004: SET_SLOT_TAG_TYPE +* Command: 3 bytes. `slot_number|tag_type[2]` with `slot_number` between 0 and 7 and `tag_type` according to `tag_specific_type_t` enum, U16 in Network byte order. +* Response: no data +* CLI: cf `hw slot type` +### 1005: SET_SLOT_DATA_DEFAULT +* Command: 3 bytes. `slot_number|tag_type[2]` with `slot_number` between 0 and 7 and `tag_type` U16 according to `tag_specific_type_t` enum, U16 in Network byte order. +* Response: no data +* CLI: cf `hw slot init` +### 1006: SET_SLOT_ENABLE +* Command: 3 bytes. `slot_number|sense_type|enable` with `slot_number` between 0 and 7, `sense_type` according to `tag_sense_type_t` enum and `enable` = `0x01` to enable, `0x00` to disable +* Response: no data +* CLI: cf `hw slot enable` +### 1007: SET_SLOT_TAG_NICK +* Command: 2+N bytes. `slot_number|sense_type|name[N]` with `slot_number` between 0 and 7, `sense_type` according to `tag_sense_type_t` enum and `name` a UTF-8 encoded string of max 32 bytes, no null terminator. +* Response: no data +* CLI: cf `hw slot nick set` +### 1008: GET_SLOT_TAG_NICK +* Command: 2 bytes. `slot_number|sense_type` with `slot_number` between 0 and 7 and `sense_type` according to `tag_sense_type_t` enum. +* Response: a UTF-8 encoded string of max 32 bytes, no null terminator. If no nick name has been recorded in Flash, response status is `STATUS_FLASH_READ_FAIL`. +* CLI: cf `hw slot nick get` +### 1009: SLOT_DATA_CONFIG_SAVE +* Command: no data +* Response: no data +* CLI: cf `hw slot update` +### 1010: ENTER_BOOTLOADER +* Command: no data +* Response: this special command does not return and will interrupt the communication link while rebooting in bootloader mode, needed for DFU. +* CLI: cf `hw dfu` +### 1011: GET_DEVICE_CHIP_ID +* Command: no data +* Response: 8 bytes. nRF `DEVICEID[8]` U64 in Network byte order. +* CLI: cf `hw chipid get` +### 1012: GET_DEVICE_ADDRESS +* Command: no data +* Response: 6 bytes. nRF `DEVICEADDR[6]` U48 in Network byte order. First 2 MSBits forced to `0b11` to match BLE static address. +* CLI: cf `hw address get` +### 1013: SAVE_SETTINGS +* Command: no data +* Response: no data +* CLI: cf `hw settings store` +### 1014: RESET_SETTINGS +* Command: no data +* Response: no data +* CLI: cf `hw settings reset` +### 1015: SET_ANIMATION_MODE +* Command: 1 byte, according to `settings_animation_mode_t` enum. +* Response: no data +* CLI: cf `hw settings animation set` +### 1016: GET_ANIMATION_MODE +* Command: no data +* Response: 1 byte, according to `settings_animation_mode_t` enum. +* CLI: cf `hw settings animation get` +### 1017: GET_GIT_VERSION +* Command: no data +* Response: n bytes, a UTF-8 encoded string, no null terminator. +* CLI: cf `hw version` +### 1018: GET_ACTIVE_SLOT +* Command: no data +* Response: 1 byte +* CLI: cf `hw slot list` +### 1019: GET_SLOT_INFO +* Command: no data +* Response: 32 bytes, 8 tuples `hf_tag_type[2]|lf_tag_type[2]` according to `tag_specific_type_t` enum, for slots from 0 to 7, U16 in Network byte order. +* CLI: cf `hw slot list` +### 1020: WIPE_FDS +* Command: no data +* Response: no data. Status is `STATUS_DEVICE_SUCCESS` or `STATUS_FLASH_WRITE_FAIL`. The device will reboot shortly after this command. +* CLI: cf `hw factory_reset` +### 1023: GET_ENABLED_SLOTS +* Command: no data +* Response: 16 bytes, 8*2 bool = `0x00` or `0x01`, 2 bytes for each slot from 0 to 7, as `enabled_hf|enabled_lf` +### 1024: DELETE_SLOT_SENSE_TYPE +* Command: 2 bytes. `slot_number|sense_type` with `slot_number` between 0 and 7 and `sense_type` according to `tag_sense_type_t` enum. +* Response: no data +* CLI: cf `hw factory_reset` +### 1025: GET_BATTERY_INFO +* Command: no data +* Response: 3 bytes, `voltage[2]|percentage`. Voltage: U16 in Network byte order. +* CLI: cf `hw battery` +### 1026: GET_BUTTON_PRESS_CONFIG +* Command: 1 byte. Char `A` or `B` (`a`/`b` tolerated too) +* Response: 1 byte, `button_function` according to `settings_button_function_t` enum. +* CLI: cf `hw settings btnpress get` +### 1027: SET_BUTTON_PRESS_CONFIG +* Command: 2 bytes. `button|button_function` with `button` char `A` or `B` (`a`/`b` tolerated too) and `button_function` according to `settings_button_function_t` enum. +* Response: no data +* CLI: cf `hw settings btnpress set` +### 1028: GET_LONG_BUTTON_PRESS_CONFIG +* Command: 1 byte. Char `A` or `B` (`a`/`b` tolerated too) +* Response: 1 byte, `button_function` according to `settings_button_function_t` enum. +* CLI: cf `hw settings btnpress get` +### 1029: SET_LONG_BUTTON_PRESS_CONFIG +* Command: 2 bytes. `button|button_function` with `button` char `A` or `B` (`a`/`b` tolerated too) and `button_function` according to `settings_button_function_t` enum. +* Response: no data +* CLI: cf `hw settings btnpress set` +### 1030: SET_BLE_PAIRING_KEY +* Command: 6 bytes. 6 ASCII-encoded digits. +* Response: no data +* CLI: cf `hw settings blekey` +### 1031: GET_BLE_PAIRING_KEY +* Command: no data +* Response: 6 bytes. 6 ASCII-encoded digits. +* CLI: cf `hw settings blekey` +### 1032: DELETE_ALL_BLE_BONDS +* Command: no data +* Response: no data +* CLI: cf `hw ble bonds clear` +### 1033: GET_DEVICE_MODEL +* Command: no data +* Response: 1 byte. `hw_version` aka `NRF_DFU_HW_VERSION` according to `chameleon_device_type_t` enum (0=Ultra, 1=Lite) +* CLI: cf `hw version` +### 1034: GET_DEVICE_SETTINGS +* Command: no data +* Response: 14 bytes + * `settings_current_version` = `5` + * `animation_mode`, cf [GET_ANIMATION_MODE](#1016-get_animation_mode) + * `btn_press_A`, cf [GET_BUTTON_PRESS_CONFIG](#1026-get_button_press_config) + * `btn_press_B`, cf [GET_BUTTON_PRESS_CONFIG](#1026-get_button_press_config) + * `btn_long_press_A`, cf [GET_LONG_BUTTON_PRESS_CONFIG](#1028-get_long_button_press_config) + * `btn_long_press_B`, cf [GET_LONG_BUTTON_PRESS_CONFIG](#1028-get_long_button_press_config) + * `ble_pairing_enable`, cf [GET_BLE_PAIRING_ENABLE](#1036-get_ble_pairing_enable) + * `ble_pairing_key[6]`, cf [GET_BLE_PAIRING_KEY](#1031-get_ble_pairing_key) +* CLI: unused +### 1035: GET_DEVICE_CAPABILITIES +* Command: no data +* Response: 2*n bytes, a list of supported commands IDs. +* CLI: used internally on connect +### 1036: GET_BLE_PAIRING_ENABLE +* Command: no data +* Response: 1 byte, bool = `0x00` or `0x01` +* CLI: cf `hw settings blepair` +### 1037: SET_BLE_PAIRING_ENABLE +* Command: 1 byte, bool = `0x00` or `0x01` +* Response: no data +* CLI: cf `hw settings blepair` +### 2000: HF14A_SCAN +* Command: no data +* Response: N bytes: `tag1_data|tag2_data|...` with each tag: `uidlen|uid[uidlen]|atqa[2]|sak|atslen|ats[atslen]`. UID, ATQA, SAK and ATS as bytes. +* CLI: cf `hf 14a scan` + +Notes: +* remind that if no tag is present, status will be `HF_TAG_NO` and Response empty. +* at the moment, the firmware supports only one tag, but get your client ready for more! +* `atslen` must not be confused with `ats[0]`==`TL`. So `atslen|ats` = `00` means no ATS while `0100` would be an empty ATS. +### 2001: MF1_DETECT_SUPPORT +* Command: no data +* Response: 1 byte, bool = `0x00` or `0x01` +* CLI: cf `hf 14a info` +### 2002: MF1_DETECT_PRNG +* Command: no data +* Response: 1 byte, according to `mf1_nested_type_t` enum +* CLI: cf `hf 14a info` +### 2003: MF1_STATIC_NESTED_ACQUIRE +* Command: 10 bytes: `type_known|block_known|key_known[6]|type_target|block_target`. Key as 6 bytes. +* Response: 4+N*8 bytes: `uid[4]` followed by N tuples of `nt[4]|nt_enc[4]`. All values as U32. +* CLI: cf `hf mf nested` on static nonce tag +### 2004: MF1_DARKSIDE_ACQUIRE +* Command: 4 bytes: `type_target|block_target|first_recover|sync_max` +* Response: 1 byte if Darkside failed, according to `mf1_darkside_status_t` enum, + else 33 bytes `darkside_status|uid[4]|nt1[4]|par[8]|ks1[8]|nr[4]|ar[4]` + * `darkside_status` + * `uid[4]` U32 (format expected by `darkside` tool) + * `nt1[4]` U32 + * `par[8]` U64 + * `ks1[8]` U64 + * `nr[4]` U32 + * `ar[4]` U32 +* CLI: cf `hf mf darkside` +### 2005: MF1_DETECT_NT_DIST +* Command: 8 bytes: `type_known|block_known|key_known[6]`. Key as 6 bytes. +* Response: 8 bytes: `uid[4]|dist[4]` + * `uid[4]` U32 (format expected by `nested` tool) + * `dist[4]` U32 +* CLI: cf `hf mf nested` +### 2006: MF1_NESTED_ACQUIRE +* Command: 10 bytes: `type_known|block_known|key_known[6]|type_target|block_target`. Key as 6 bytes. +* Response: N*9 bytes: N tuples of `nt[4]|nt_enc[4]|par` + * `nt[4]` U32 + * `nt_enc[4]` U32 + * `par` +* CLI: cf `hf mf nested` +### 2007: MF1_AUTH_ONE_KEY_BLOCK +* Command: 8 bytes: `type|block|key[6]`. Key as 6 bytes. +* Response: no data +* Status will be `HF_TAG_OK` if auth succeeded, else `MF_ERR_AUTH` +* CLI: cf `hf mf nested` +### 2008: MF1_READ_ONE_BLOCK +* Command: 8 bytes: `type|block|key[6]`. Key as 6 bytes. +* Response: 16 bytes: `block_data[16]` +* CLI: cf `hf mf rdbl` +### 2009: MF1_WRITE_ONE_BLOCK +* Command: 24 bytes: `type|block|key[6]|block_data[16]`. Key as 6 bytes. +* Response: no data +* CLI: cf `hf mf wrbl` +### 2010: HF14A_RAW +* Command: : 5+N bytes: `options|resp_timeout_ms[2]|bitlen[2]` followed by data to be transmitted, with `options` a 1-byte BigEndian bitfield, so starting from MSB: + * `activate_rf_field`:1 + * `wait_response`:1 + * `append_crc`:1 + * `auto_select`:1 + * `keep_rf_field`:1 + * `check_response_crc`:1 + * `reserved`:2 +* Response: data sent by the card +* CLI: cf `hf 14a raw` +### 3000: EM410X_SCAN +* Command: no data +* Response: 5 bytes. `id[5]`. ID as 5 bytes. +* CLI: cf `lf em read` +### 3001: EM410X_WRITE_TO_T55XX +* Command: 9+N*4 bytes: `id[5]|new_key[4]|old_key1[4]|old_key2[4]|...` (N>=1). . ID as 5 bytes. Keys as 4 bytes. +* Response: no data +* CLI: cf `lf em write` +### 4000: MF1_WRITE_EMU_BLOCK_DATA +* Command: 1+N*16 bytes: `block_start|block_data1[16]|block_data2[16]|...` (1<=N<=31) +* Response: no data +* CLI: cf `hf mf eload` +### 4001: HF14A_SET_ANTI_COLL_DATA +* Command: N bytes: `uidlen|uid[uidlen]|atqa[2]|sak|atslen|ats[atslen]`. UID, ATQA, SAK and ATS as bytes. +* Response: no data +* CLI: cf `hf mf sim` +### 4004: MF1_SET_DETECTION_ENABLE +* Command: 1 byte, bool = `0x00` or `0x01` +* Response: no data +* CLI: cf `hf detection enable` +### 4005: MF1_GET_DETECTION_COUNT +* Command: no data +* Response: 4 bytes, `count[4]`, U32 in Network byte order. +* CLI: cf `hf detection count` +### 4006: MF1_GET_DETECTION_LOG +* Command: 4 bytes, `index`, U32 in Network byte order. +* Response: N*18 bytes. 0<=N<=28 + * `block` + * `...|is_nested|is_key_b` 1-byte bitfield, starting from LSB + * `uid[4]` ? + * `nt[4]` ? + * `nr[4]` ? + * `ar[4]` ? +* CLI: cf `hf detection decrypt` +### 4007: MF1_GET_DETECTION_ENABLE +* Command: no data +* Response: 1 byte, bool = `0x00` or `0x01` +* CLI: cf `hw slot list` +### 4008: MF1_READ_EMU_BLOCK_DATA +* Command: 2 bytes: `block_start|block_count` with 1<=`block_count` <=32 +* Response: `block_count`*16 bytes +* CLI: cf `hf mf eread` +### 4009: MF1_GET_EMULATOR_CONFIG +* Command: no data +* Response: 5 bytes + * `detection`, cf [MF1_GET_DETECTION_ENABLE](#4007-mf1_get_detection_enable) + * `gen1a_mode`, cf [MF1_GET_GEN1A_MODE](#4010-mf1_get_gen1a_mode) + * `gen2_mode`, cf [MF1_GET_GEN2_MODE](#4012-mf1_get_gen2_mode) + * `block_anti_coll_mode`, cf [MF1_GET_BLOCK_ANTI_COLL_MODE](#4014-mf1_get_block_anti_coll_mode) + * `write_mode`, cf [MF1_GET_WRITE_MODE](#4016-mf1_get_write_mode) +* CLI: cf `hw slot list` +### 4010: MF1_GET_GEN1A_MODE +* Command: no data +* Response: 1 byte, bool = `0x00` or `0x01` +* CLI: unused +### 4011: MF1_SET_GEN1A_MODE +* Command: 1 byte, bool = `0x00` or `0x01` +* Response: no data +* CLI: cf `hf mf settings` +### 4012: MF1_GET_GEN2_MODE +* Command: no data +* Response: 1 byte, bool = `0x00` or `0x01` +* CLI: unused +### 4013: MF1_SET_GEN2_MODE +* Command: 1 byte, bool = `0x00` or `0x01` +* Response: no data +* CLI: cf `hf mf settings` +### 4014: MF1_GET_BLOCK_ANTI_COLL_MODE +* Command: no data +* Response: 1 byte, bool = `0x00` or `0x01` +* CLI: unused +### 4015: MF1_SET_BLOCK_ANTI_COLL_MODE +* Command: 1 byte, bool = `0x00` or `0x01` +* Response: no data +* CLI: cf `hf mf settings` +### 4016: MF1_GET_WRITE_MODE +* Command: no data +* Response: 1 byte, according to `nfc_tag_mf1_write_mode_t` aka `MifareClassicWriteMode` enum +* CLI: unused +### 4017: MF1_SET_WRITE_MODE +* Command: 1 byte, according to `nfc_tag_mf1_write_mode_t` aka `MifareClassicWriteMode` enum +* Response: no data +* CLI: cf `hf mf settings` +### 4018: HF14A_GET_ANTI_COLL_DATA +* Command: no data +* Response: no data or N bytes: `uidlen|uid[uidlen]|atqa[2]|sak|atslen|ats[atslen]`. UID, ATQA, SAK and ATS as bytes. +* CLI: cf `hf mf info` +### 5000: EM410X_SET_EMU_ID +* Command: 5 bytes. `id[5]`. ID as 5 bytes. +* Response: no data +* CLI: cf `lf em sim set` +### 5001: EM410X_GET_EMU_ID +* Command: no data +* Response: 5 bytes. `id[5]`. ID as 5 bytes. +* CLI: cf `lf em sim get` + +## New data payloads: guidelines for developers + +If you need to define new payloads for new commands, try to follow these guidelines. + +### Guideline: Verbose and explicit +Be verbose, explicit and reuse conventions, in order to enhance code maintainability and understandability for the other contributors +### Guideline: Structs +- Define C `struct` for cmd/resp data greater than a single byte, use and abuse of `struct.pack`/`struct.unpack` in Python. So one can understand the payload format at a simple glimpse. Exceptions to `C` struct are when the formats are of variable length (but Python `struct` is still flexible enough to cope with such formats!) +- Avoid hardcoding offsets, use `sizeof()`, `offsetof(struct, field)` in C and `struct.calcsize()` in Python +- For complex bitfield structs, exceptionally you can use ctypes in Python. Beware ctypes.BigEndianStructure bitfield will be parsed in the firmware in the reverse order, from LSB to MSB. +### Guideline: Status +If single byte of data to return, still use a 1-byte `data`, not `status`. Standard response status is `STATUS_DEVICE_SUCCESS` for general commands, `HF_TAG_OK` for HF commands and `LF_TAG_OK` for LF commands. If the response status is different than those, the response data is empty. Response status are generic and cover things like tag disappearance or tag non-conformities with the ISO standard. If a command needs more specific response status, it is added in the first byte of the data, to avoid cluttering the 1-byte general status enum with command-specific statuses. See e.g. [MF1_DARKSIDE_ACQUIRE](#2004-mf1_darkside_acquire). +### Guideline: unambiguous types +- Use unambiguous types such as `uint16_t`, not `int` or `enum`. Cast explicitly `int` and `enum` to `uint_t` of proper size +- Use Network byte order for 16b and 32b integers + - Macros `U16NTOHS`, `U32NTOHL` must be used on reception of a command payload. + - Macros `U16HTONS`, `U32HTONL` must be used on creation of a response payload. + - In Python, use the modifier `!` with all `struct.pack`/`struct.unpack` +### Guideline: payload parsing in handlers +- Concentrate payload parsing in the handlers, avoid further parsing in their callers. Callers should not care about the protocol. This is true for the firmware and the client. +- In cmd_processor handlers: don't reuse input `length`/`data` parameters for creating the response content +### Guideline: Naming conventions +- Use the exact same command and fields names in firmware and in client, use function names matching the command names for their handlers unless there is a very good reason not to do so. This helps grepping around. Names must start with a letter, not a number, because some languages require it (e.g. `14a_scan` not possible in Python) +- Respect commands order in `m_data_cmd_map`, `data_cmd.h` and `chameleon_cmd.py` definitions +- Even if a command is not yet implemented in firmware or in client but a command number is allocated, add it to `data_cmd.h` and `chameleon_cmd.py` with some `FIXME: to be implemented` comment +### Guideline: Validate status and data +- Validate response status in client before parsing data. +- Validate data before using it. diff --git a/docs/technical_whitepaper.md b/docs/technical_whitepaper.md index e2adbf5e..bf6f887d 100644 --- a/docs/technical_whitepaper.md +++ b/docs/technical_whitepaper.md @@ -5,58 +5,67 @@ # ChameleonUltra Why not keep using ATXMEGA128? -First of all, it is difficult to buy chips because the lead time for the main chip is too long, and because the price has skyrocketed. Secondly, because the interaction speed of the ATXMEGA, emulation is slow, the decryption performance of the READER mode cannot meet the needs, and the LF support cannot be added, so we have been trying to upgrade it, such as using the latest ARM to replace the AVR framework, and the performance will definitely be greatly improved. +First of all, it is difficult to buy chips because the lead time for the main chip is too long, and because the price +has skyrocketed. Secondly, because the interaction speed of the ATXMEGA, emulation is slow, the decryption performance +of the READER mode cannot meet the needs, and the LF support cannot be added, so we have been trying to upgrade it, such +as using the latest ARM to replace the AVR framework, and the performance will definitely be greatly improved. # Why nRF52840? -NRF52840 has a built-in NFC Tag-A module, but no one seems to care about it. After playing with HydraNFC's TRF7970A and FlipperZero's ST25R3916, the developers found that they can only emulate MIFARE Classic with a very high FDT. -We accidentally tested the NFC of nRF52840, and found that it is not only surprisingly easy to emulate a complete MIFARE Classic card, but also has very good emulation performance, friendly data flow interaction, and very fast response, unlike the former which is limited by the SPI bus clock rate. We also found that it has ultra-low power consumption, ultra-small size, 256kb/1M large RAM and Flash, also has BLE5.0 and USB2.0 FS, super CortexM4F, most importantly, it is very cheap! This is undoubtedly a treasure discovery for us! +NRF52840 has a built-in NFC Tag-A module, but no one seems to care about it. After playing with HydraNFC's TRF7970A and +FlipperZero's ST25R3916, the developers found that they can only emulate MIFARE Classic with a very high FDT. +We accidentally tested the NFC of nRF52840, and found that it is not only surprisingly easy to emulate a complete MIFARE +Classic card, but also has very good emulation performance, friendly data flow interaction, and very fast response, +unlike the former which is limited by the SPI bus clock rate. We also found that it has ultra-low power consumption, +ultra-small size, 256kb/1M large RAM and Flash, also has BLE5.0 and USB2.0 FS, super CortexM4F, most importantly, it is +very cheap! This is undoubtedly a treasure discovery for us! -Below we will explain in detail how we exploited the performance of the NRF52840, and what seemingly impossible functions have been realized with it! +Below we will explain in detail how we exploited the performance of the NRF52840, and what seemingly impossible +functions have been realized with it! # Supported functions ## High Frequency Attack -| Attack Type | Tag Type | Whether the hardware supports | Does the software support | Whether the application layer supports | Note | -|--------------|:-------------:|------------------------------:|---------------------------|:--------------------------------------:|------------------------:| -| Sniffing | No | No | No | No | | +| Attack Type | Tag Type | Whether the hardware supports | Does the software support | Whether the application layer supports | Note | +|--------------|:--------------:|------------------------------:|---------------------------|:--------------------------------------:|-------------------------:| +| Sniffing | No | No | No | No | | | MFKEY32 V2 | MIFARE Classic | Support | Support | Support | MIFARE Classic Detection | -| Darkside | MIFARE Classic | Support | Support | Support | Encrypted 4 bit NAck | -| Nested | MIFARE Classic | Support | Support | Support | PRNG(Distance guess) | -| StaticNested | MIFARE Classic | Support | Support | Not yet implemented | PRNG(2NT Fast Decrypt) | -| HardNested | MIFARE Classic | Support | Support | Not yet implemented | No | -| Relay attack | ISO14443A | Support | Support | Not yet implemented | No | +| Darkside | MIFARE Classic | Support | Support | Support | Encrypted 4 bit NAck | +| Nested | MIFARE Classic | Support | Support | Support | PRNG(Distance guess) | +| StaticNested | MIFARE Classic | Support | Support | Support | PRNG(2NT Fast Decrypt) | +| HardNested | MIFARE Classic | Support | Support | Not yet implemented | No | +| Relay attack | ISO14443A | Support | Support | Not yet implemented | No | ## High Frequency emulation -| Card Type | Encoding Type | Whether the hardware supports | Does the software support | Whether the application layer supports | Note | -|-------------------------------|:--------------------:|------------------------------:|---------------------------|:--------------------------------------:|-----------------------------------------:| -| Other than ISO14443A | No | No | No | No | [NRF52 NFC Module][nrf52_nfc_module_doc] | -| NTAG 21x (210-218) | ISO14443A/106 kbit/s | Support | Support | Not yet implemented | | -| MIFARE Ultralight | ISO14443A/106 kbit/s | Support | Support | Not yet implemented | | -| MIFARE Ultralight Ev1 | ISO14443A/106 kbit/s | Support | Support | Not yet implemented | | -| MIFARE Ultralight C | ISO14443A/106 kbit/s | Support | Support | Not yet implemented | | +| Card Type | Encoding Type | Whether the hardware supports | Does the software support | Whether the application layer supports | Note | +|--------------------------------|:--------------------:|------------------------------:|---------------------------|:--------------------------------------:|-----------------------------------------:| +| Other than ISO14443A | No | No | No | No | [NRF52 NFC Module][nrf52_nfc_module_doc] | +| NTAG 21x (210-218) | ISO14443A/106 kbit/s | Support | Support | Not yet implemented | | +| MIFARE Ultralight | ISO14443A/106 kbit/s | Support | Support | Not yet implemented | | +| MIFARE Ultralight Ev1 | ISO14443A/106 kbit/s | Support | Support | Not yet implemented | | +| MIFARE Ultralight C | ISO14443A/106 kbit/s | Support | Support | Not yet implemented | | | MIFARE Classic1K/2K/4K (4B/7B) | ISO14443A/106 kbit/s | Support | Support | Support | | -| MIFARE DESFire | ISO14443A High Rate | Only supported Low rate | Only supported Low rate | Not yet implemented | | -| MIFARE DESFire EV1 | ISO14443A High rate | Only supported Low rate | Only supported Low rate | Not yet implemented | Backward compatible | -| MIFARE DESFire EV2 | ISO14443A High rate | Only supported Low rate | Only supported Low rate | Not yet implemented | | -| MIFARE Plus | ISO14443A High rate | Only supported Low rate | Only supported Low rate | Not yet implemented | | +| MIFARE DESFire | ISO14443A High Rate | Only supported Low rate | Only supported Low rate | Not yet implemented | | +| MIFARE DESFire EV1 | ISO14443A High rate | Only supported Low rate | Only supported Low rate | Not yet implemented | Backward compatible | +| MIFARE DESFire EV2 | ISO14443A High rate | Only supported Low rate | Only supported Low rate | Not yet implemented | | +| MIFARE Plus | ISO14443A High rate | Only supported Low rate | Only supported Low rate | Not yet implemented | | ## High Frequency Reader -| Card Type | Encoding Type | Whether the hardware supports | Does the software support | Whether the application layer supports | Note | -|-------------------------------|:--------------------:|---------------------------------------------:|----------------------------------------------|:--------------------------------------:|-------------------------------------------:| -| Non <13.56MHz or ISO14443A> | No | No | No | No | [NXP RC522 Datasheet][nxp_rc522_datasheet] | -| NTAG 21x (210-218) | ISO14443A/106 kbit/s | Support | Support | Not yet implemented | | -| MIFARE Ultralight | ISO14443A/106 kbit/s | Support | Support | Not yet implemented | | -| MIFARE Ultralight Ev1 | ISO14443A/106 kbit/s | Support | Support | Not yet implemented | | -| MIFARE Ultralight C | ISO14443A/106 kbit/s | Support | Support | Not yet implemented | | +| Card Type | Encoding Type | Whether the hardware supports | Does the software support | Whether the application layer supports | Note | +|---------------------------------|:--------------------:|---------------------------------------------:|----------------------------------------------|:--------------------------------------:|-------------------------------------------:| +| Non <13.56MHz or ISO14443A> | No | No | No | No | [NXP RC522 Datasheet][nxp_rc522_datasheet] | +| NTAG 21x (210-218) | ISO14443A/106 kbit/s | Support | Support | Not yet implemented | | +| MIFARE Ultralight | ISO14443A/106 kbit/s | Support | Support | Not yet implemented | | +| MIFARE Ultralight Ev1 | ISO14443A/106 kbit/s | Support | Support | Not yet implemented | | +| MIFARE Ultralight C | ISO14443A/106 kbit/s | Support | Support | Not yet implemented | | | MIFARE Classic 1K/2K/4K (4B/7B) | ISO14443A/106 kbit/s | Support | Support | Support | | -| MIFARE DESFire | ISO14443A High Rate | Supports low rates, or possibly higher rates | Supports low rates, or possibly higher rates | Not yet implemented | | -| MIFARE DESFire EV1 | ISO14443A High rate | Supports low rates, or possibly higher rates | Supports low rates, or possibly higher rates | Not yet implemented | Backward compatible | -| MIFARE DESFire EV2 | ISO14443A High rate | Supports low rates, or possibly higher rates | Supports low rates, or possibly higher rates | Not yet implemented | | -| MIFARE Plus | ISO14443A High rate | Supports low rates, or possibly higher rates | Supports low rates, or possibly higher rates | Not yet implemented | | +| MIFARE DESFire | ISO14443A High Rate | Supports low rates, or possibly higher rates | Supports low rates, or possibly higher rates | Not yet implemented | | +| MIFARE DESFire EV1 | ISO14443A High rate | Supports low rates, or possibly higher rates | Supports low rates, or possibly higher rates | Not yet implemented | Backward compatible | +| MIFARE DESFire EV2 | ISO14443A High rate | Supports low rates, or possibly higher rates | Supports low rates, or possibly higher rates | Not yet implemented | | +| MIFARE Plus | ISO14443A High rate | Supports low rates, or possibly higher rates | Supports low rates, or possibly higher rates | Not yet implemented | | ## Low Frequency Attack @@ -67,103 +76,113 @@ Below we will explain in detail how we exploited the performance of the NRF52840 ## Low Frequency emulation -| Card Type | Encoding Type | Whether the hardware supports | Does the software support | Whether the application layer supports | Note | -|--------------------------|:-------------:|------------------------------:|---------------------------|:--------------------------------------:|----------------------------------------------:| +| Card Type | Encoding Type | Whether the hardware supports | Does the software support | Whether the application layer supports | Note | +|---------------------------------|:-------------:|------------------------------:|---------------------------|:--------------------------------------:|----------------------------------------------:| | Other than <125KHz/ASK/PSK/FSK> | No | No | No | No | Only 125 khz RF, Modulation ASK, FSK and PSK. | -| EM410x | ASK | Support | Support | Support | EM4100 is support(AD 64bit) | -| T5577 | ASK | Support | Support | Not yet implemented | | -| EM4306 | ASK | Support | Support | Not yet implemented | | -| HID Prox | FSK | Support | Support | Not yet implemented | | -| Indala | PSK | Support | Support | Not yet implemented | | -| FDX-B | ASK | Support | Support | Not yet implemented | | -| Paradox | FSK | Support | Support | Not yet implemented | | -| Keri | PSK | Support | Support | Not yet implemented | | -| AWD | FSK | Support | Support | Not yet implemented | | -| ioProx | FSK | Support | Support | Not yet implemented | | -| securakey | ASK | Support | Support | Not yet implemented | | -| gallagher | ASK | Support | Support | Not yet implemented | | -| PAC/Stanley | ASK | Support | Support | Not yet implemented | | -| Presco | ASK | Support | Support | Not yet implemented | | -| Visa2000 | ASK | Support | Support | Not yet implemented | | -| Viking | ASK | Support | Support | Not yet implemented | | -| Noralsy | ASK | Support | Support | Not yet implemented | | -| NexWatch | PSK | Support | Support | Not yet implemented | | -| Jablotron | ASK | Support | Support | Not yet implemented | | +| EM410x | ASK | Support | Support | Support | EM4100 is support(AD 64bit) | +| T5577 | ASK | Support | Support | Not yet implemented | | +| EM4305 | ASK | Support | Support | Not yet implemented | | +| HID Prox | FSK | Support | Support | Not yet implemented | | +| Indala | PSK | Support | Support | Not yet implemented | | +| FDX-B | ASK | Support | Support | Not yet implemented | | +| Paradox | FSK | Support | Support | Not yet implemented | | +| Keri | PSK | Support | Support | Not yet implemented | | +| AWD | FSK | Support | Support | Not yet implemented | | +| ioProx | FSK | Support | Support | Not yet implemented | | +| securakey | ASK | Support | Support | Not yet implemented | | +| gallagher | ASK | Support | Support | Not yet implemented | | +| PAC/Stanley | ASK | Support | Support | Not yet implemented | | +| Presco | ASK | Support | Support | Not yet implemented | | +| Visa2000 | ASK | Support | Support | Not yet implemented | | +| Viking | ASK | Support | Support | Not yet implemented | | +| Noralsy | ASK | Support | Support | Not yet implemented | | +| NexWatch | PSK | Support | Support | Not yet implemented | | +| Jablotron | ASK | Support | Support | Not yet implemented | | ## Low Frequency Reader -| Card Type | Encoding Type | Whether the hardware supports | Does the software support | Whether the application layer supports | Note | -|--------------------------|:-------------:|------------------------------:|---------------------------|:--------------------------------------:|----------------------------------------------:| +| Card Type | Encoding Type | Whether the hardware supports | Does the software support | Whether the application layer supports | Note | +|---------------------------------|:-------------:|------------------------------:|---------------------------|:--------------------------------------:|----------------------------------------------:| | Other than <125KHz/ASK/PSK/FSK> | No | No | No | No | Only 125 khz RF, Modulation ASK, FSK and PSK. | -| EM410x | ASK | Support | Support | Support | | -| T5577 | ASK | Support | Support | Support(Write) | | -| EM4305 | ASK | Support | Support | Not yet implemented | | -| HID Prox | FSK | Support | Support | Not yet implemented | | -| Indala | PSK | Support | Support | Not yet implemented | | -| FDX-B | ASK | Support | Support | Not yet implemented | | -| Paradox | FSK | Support | Support | Not yet implemented | | -| Keri | PSK | Support | Support | Not yet implemented | | -| AWD | FSK | Support | Support | Not yet implemented | | -| ioProx | FSK | Support | Support | Not yet implemented | | -| securakey | ASK | Support | Support | Not yet implemented | | -| gallagher | ASK | Support | Support | Not yet implemented | | -| PAC/Stanley | ASK | Support | Support | Not yet implemented | | -| Presco | ASK | Support | Support | Not yet implemented | | -| Visa2000 | ASK | Support | Support | Not yet implemented | | -| Viking | ASK | Support | Support | Not yet implemented | | -| Noralsy | ASK | Support | Support | Not yet implemented | | -| NexWatch | PSK | Support | Support | Not yet implemented | | -| Jablotron | ASK | Support | Support | Not yet implemented | | +| EM410x | ASK | Support | Support | Support | | +| T5577 | ASK | Support | Support | Support(Write) | | +| EM4305 | ASK | Support | Support | Not yet implemented | | +| HID Prox | FSK | Support | Support | Not yet implemented | | +| Indala | PSK | Support | Support | Not yet implemented | | +| FDX-B | ASK | Support | Support | Not yet implemented | | +| Paradox | FSK | Support | Support | Not yet implemented | | +| Keri | PSK | Support | Support | Not yet implemented | | +| AWD | FSK | Support | Support | Not yet implemented | | +| ioProx | FSK | Support | Support | Not yet implemented | | +| securakey | ASK | Support | Support | Not yet implemented | | +| gallagher | ASK | Support | Support | Not yet implemented | | +| PAC/Stanley | ASK | Support | Support | Not yet implemented | | +| Presco | ASK | Support | Support | Not yet implemented | | +| Visa2000 | ASK | Support | Support | Not yet implemented | | +| Viking | ASK | Support | Support | Not yet implemented | | +| Noralsy | ASK | Support | Support | Not yet implemented | | +| NexWatch | PSK | Support | Support | Not yet implemented | | +| Jablotron | ASK | Support | Support | Not yet implemented | | ## Low Frequency Modulation - -| Modulation Type | wav | -|-----------------|----------------------------:| +| Modulation Type | wav | +|-----------------|------------------------------------:| | PSK | ![PSK WAV](images/measured-psk.png) | | FSK | ![FSK WAV](images/measured-fsk.png) | | ASK | ![ASK WAV](images/measured-ask.png) | # Ultra-low power consumption -It integrates a high-performance and low-power NFC module inside. When the NFC unit is turned on, the total current of the chip is only 5mA@3.3V. +It integrates a high-performance and low-power NFC module inside. When the NFC unit is turned on, the total current of +the chip is only 5mA@3.3V. The underlying interaction is done independently by the NFC unit and does not occupy the CPU. -In addition, the nRF52840 itself is a high-performance low-power BLE chip, and the encryption and calculation process is only 7mA@3.3V. It can greatly reduce the battery volume and prolong the working time. That is to say, the 35mAh 10mm*40mm button lithium battery can guarantee to be charged once every half a year under the working condition of swiping the card 8 times a day for 3 seconds each time. Full potential for everyday use. +In addition, the nRF52840 itself is a high-performance low-power BLE chip, and the encryption and calculation process is +only 7mA@3.3V. It can greatly reduce the battery volume and prolong the working time. That is to say, the 35mAh 10mm* +40mm button lithium battery can guarantee to be charged once every half a year under the working condition of swiping +the card 8 times a day for 3 seconds each time. Full potential for everyday use. # Not just UID, but a real and complete MIFARE Classic emulation -We can easily and completely emulate all data and password verification of all sectors, and can customize SAK, ATQA, ATS, etc. Similar to an open CPU card development platform, 14A interaction of various architectures can be easily realized. +We can easily and completely emulate all data and password verification of all sectors, and can customize SAK, ATQA, +ATS, etc. Similar to an open CPU card development platform, 14A interaction of various architectures can be easily +realized. # Super compatibility with low-power locks using batteries -The structure of the old Chameleon AVR is slow to start during emulation. Faced with a battery-powered low-power lock and an integrated lock on the door, it will be frequently interrupted, and the verification interaction cannot be completed completely, resulting in no response when swiping the card. +The structure of the old Chameleon AVR is slow to start during emulation. Faced with a battery-powered low-power lock +and an integrated lock on the door, it will be frequently interrupted, and the verification interaction cannot be +completed completely, resulting in no response when swiping the card. -In order to reduce power consumption, the battery lock will send out a field signal as short as possible when searching for a card, which is no problem for the original card, but it is fatal for the MCU emulated card. Cards or mobile smart bracelets emulated by the MCU cannot wake up and respond in such a short time, so many battery locks cannot open the door, which greatly reduces the user experience. +In order to reduce power consumption, the battery lock will send out a field signal as short as possible when searching +for a card, which is no problem for the original card, but it is fatal for the MCU emulated card. Cards or mobile smart +bracelets emulated by the MCU cannot wake up and respond in such a short time, so many battery locks cannot open the +door, which greatly reduces the user experience. -This project specially optimizes the start-up and interaction logic and antenna for low-power reading heads. After testing a variety of common low-power reading heads, they can open the door perfectly by swiping the card. +This project specially optimizes the start-up and interaction logic and antenna for low-power reading heads. After +testing a variety of common low-power reading heads, they can open the door perfectly by swiping the card. # Ultra-fast response speed and low interaction delay(MIFARE Classic) -| Tag/Emulation | FDT | "**_FDT_**" Rating | -|----------------------|:---------------------------:|:--------------------------------------------------------------------------------:| -| Standard MIFARE Card | ![Standard_m1_s50](images/fdt_standard_s50.png) | ⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐ | -| Chameleon Ultra | ![Chameleon Ultra](images/fdt_chameleon_ultra.png) | ⭐⭐⭐⭐⭐⭐⭐⭐ | -| Proxmark3 Rdv4.01 | ![Proxmark3_Rdv4_RRG_(Firmware build at 20201026)](images/fdt_pm3_rdv401.png) | ⭐⭐⭐⭐ | -| RedMi K30 | ![Xiaomi_k30u_smartkey](images/fdt_redmi_k30.png) | ⭐⭐⭐⭐⭐⭐ | -| Chameleon Tiny | ![Chameleon Tiny](images/fdt_chameleon_tiny.png) | ⭐⭐⭐⭐⭐ | -| Flipper Zero | ![Flipper Zero](images/fdt_flipper_zero.png) | ⭐⭐ | +| Tag/Emulation | FDT | "**_FDT_**" Rating | +|----------------------|:-----------------------------------------------------------------------------:|:--------------------------------------------------------------------------------:| +| Standard MIFARE Card | ![Standard_m1_s50](images/fdt_standard_s50.png) | ⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐ | +| Chameleon Ultra | ![Chameleon Ultra](images/fdt_chameleon_ultra.png) | ⭐⭐⭐⭐⭐⭐⭐⭐ | +| Proxmark3 Rdv4.01 | ![Proxmark3_Rdv4_RRG_(Firmware build at 20201026)](images/fdt_pm3_rdv401.png) | ⭐⭐⭐⭐ | +| RedMi K30 | ![Xiaomi_k30u_smartkey](images/fdt_redmi_k30.png) | ⭐⭐⭐⭐⭐⭐ | +| Chameleon Tiny | ![Chameleon Tiny](images/fdt_chameleon_tiny.png) | ⭐⭐⭐⭐⭐ | +| Flipper Zero | ![Flipper Zero](images/fdt_flipper_zero.png) | ⭐⭐ | # 256kB super large RAM cooperates with RC522 to enable attacks - -| Attack Type | CLI | -|--------------|:------------------------------:| -| MFKEY32 V2 | ![attack_MIFARE_mfkey32](images/cli_mfkey32v2.png) | -| Darkside | ![attack_MIFARE_darkside](images/cli_darkside.png) | -| Nested | ![attack_MIFARE_nested](images/cli_nested.png) | -| StaticNested | Coming Soon | -| HardNested | Coming Soon | -| Relay attack | Coming Soon | +| Attack Type | CLI | +|--------------|:----------------------------------------------------------:| +| MFKEY32 V2 | ![attack_MIFARE_mfkey32](images/cli_mfkey32v2.png) | +| Darkside | ![attack_MIFARE_darkside](images/cli_darkside.png) | +| Nested | ![attack_MIFARE_nested](images/cli_nested.png) | +| StaticNested | ![attack_MIFARE_staticnested](images/cli_staticnested.jpg) | +| HardNested | Coming Soon | +| Relay attack | Coming Soon | # Hardware frame diagram diff --git a/firmware/Makefile.defs b/firmware/Makefile.defs index d345c8f5..7f7314dd 100644 --- a/firmware/Makefile.defs +++ b/firmware/Makefile.defs @@ -26,7 +26,10 @@ CHAMELEON_LITE := lite CURRENT_DEVICE_TYPE ?= ${CHAMELEON_ULTRA} # Versioning information -GIT_VERSION := "$(shell git describe --abbrev=7 --dirty --always --tags)" +GIT_VERSION := $(shell git describe --abbrev=7 --dirty --always --tags) +APP_FW_SEMVER := $(subst v,,$(shell git describe --tags --abbrev=0 --match "v*.*")) +APP_FW_VER_MAJOR := $(word 1,$(subst ., ,$(APP_FW_SEMVER))) +APP_FW_VER_MINOR := $(word 2,$(subst ., ,$(APP_FW_SEMVER))) # Enable NRF_LOG on SWO pin as UART TX NRF_LOG_UART_ON_SWO_ENABLED := 0 diff --git a/firmware/application/Makefile b/firmware/application/Makefile index 9e02889d..632b3e28 100644 --- a/firmware/application/Makefile +++ b/firmware/application/Makefile @@ -401,6 +401,13 @@ CFLAGS += -fno-builtin -fshort-enums # Versioning flags CFLAGS += -DGIT_VERSION=\"$(GIT_VERSION)\" +ifndef APP_FW_VER_MAJOR +$(error APP_FW_VER_MAJOR is not defined, please define it) +endif +ifndef APP_FW_VER_MINOR +$(error APP_FW_VER_MINOR is not defined, please define it) +endif +CFLAGS += -DAPP_FW_VER_MAJOR=$(APP_FW_VER_MAJOR) -DAPP_FW_VER_MINOR=$(APP_FW_VER_MINOR) # C++ flags common to all targets CXXFLAGS += $(OPT) diff --git a/firmware/application/src/app_cmd.c b/firmware/application/src/app_cmd.c index 9400edbd..5332c09c 100644 --- a/firmware/application/src/app_cmd.c +++ b/firmware/application/src/app_cmd.c @@ -14,7 +14,7 @@ #include "nrf_pwr_mgmt.h" #include "settings.h" #include "delayed_reset.h" -#include "lwip_def.h" +#include "netdata.h" #define NRF_LOG_MODULE_NAME app_cmd @@ -24,59 +24,62 @@ NRF_LOG_MODULE_REGISTER(); +static void change_slot_auto(uint8_t slot) { + device_mode_t mode = get_device_mode(); + tag_emulation_change_slot(slot, mode != DEVICE_MODE_READER); + light_up_by_slot(); + set_slot_light_color(RGB_RED); +} -data_frame_tx_t *cmd_processor_get_version(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - uint16_t version = FW_VER_NUM; - return data_frame_make(cmd, status, 2, (uint8_t *)&version); + +static data_frame_tx_t *cmd_processor_get_app_version(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + struct { + uint8_t version_major; + uint8_t version_minor; + } PACKED payload; + payload.version_major = APP_FW_VER_MAJOR; + payload.version_minor = APP_FW_VER_MINOR; + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, sizeof(payload), (uint8_t *)&payload); } -data_frame_tx_t *cmd_processor_get_git_version(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - return data_frame_make(cmd, status, strlen(GIT_VERSION), (uint8_t *)GIT_VERSION); +static data_frame_tx_t *cmd_processor_get_git_version(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, strlen(GIT_VERSION), (uint8_t *)GIT_VERSION); } -data_frame_tx_t *cmd_processor_get_device(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { -#if defined(PROJECT_CHAMELEON_ULTRA) - uint8_t device = 1; -#else - uint8_t device = 0; -#endif - return data_frame_make(cmd, status, 1, &device); +static data_frame_tx_t *cmd_processor_get_device_model(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + uint8_t resp_data = hw_get_device_type(); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, sizeof(resp_data), &resp_data); } -data_frame_tx_t *cmd_processor_change_device_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == 1) { - if (data[0] == 1) { +static data_frame_tx_t *cmd_processor_change_device_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if ((length != 1) || (data[0] > 1)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + } + + if (data[0] == 1) { #if defined(PROJECT_CHAMELEON_ULTRA) - reader_mode_enter(); - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); + reader_mode_enter(); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); #else - return data_frame_make(cmd, STATUS_NOT_IMPLEMENTED, 0, NULL); + return data_frame_make(cmd, STATUS_NOT_IMPLEMENTED, 0, NULL); #endif - } else { + } else { #if defined(PROJECT_CHAMELEON_ULTRA) - tag_mode_enter(); + tag_mode_enter(); #endif - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); - } - } else { - return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } } -data_frame_tx_t *cmd_processor_get_device_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - device_mode_t mode = get_device_mode(); - if (mode == DEVICE_MODE_READER) { - status = 1; - } else { - status = 0; - } - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, (uint8_t *)&status); +static data_frame_tx_t *cmd_processor_get_device_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + uint8_t resp_data = (get_device_mode() == DEVICE_MODE_READER); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, sizeof(resp_data), &resp_data); } -data_frame_tx_t *cmd_processor_enter_bootloader(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_enter_bootloader(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { // restart to boot #define BOOTLOADER_DFU_GPREGRET_MASK (0xB0) #define BOOTLOADER_DFU_START_BIT_MASK (0x01) @@ -86,39 +89,48 @@ data_frame_tx_t *cmd_processor_enter_bootloader(uint16_t cmd, uint16_t status, u nrf_pwr_mgmt_shutdown(NRF_PWR_MGMT_SHUTDOWN_GOTO_DFU); // Never into here... while (1) __NOP(); + // For the compiler to be happy... + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_get_device_chip_id(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - uint32_t chip_id[2]; - chip_id[0] = NRF_FICR->DEVICEID[0]; - chip_id[1] = NRF_FICR->DEVICEID[1]; - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 8, (uint8_t *)(&chip_id[0])); +static data_frame_tx_t *cmd_processor_get_device_chip_id(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + struct { + uint32_t chip_HSW; + uint32_t chip_LSW; + } PACKED payload; + payload.chip_LSW = U32HTONL(NRF_FICR->DEVICEID[0]); + payload.chip_HSW = U32HTONL(NRF_FICR->DEVICEID[1]); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, sizeof(payload), (uint8_t *)&payload); } -data_frame_tx_t *cmd_processor_get_device_address(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - uint32_t device_address[2]; +static data_frame_tx_t *cmd_processor_get_device_address(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { // The FICR value is a just a random number, with no knowledge // of the Bluetooth Specification requirements for random addresses. // So we need to set a Bluetooth LE random address as a static address. // See: https://github.com/zephyrproject-rtos/zephyr/blob/7b6b1328a0cb96fe313a5e2bfc57047471df236e/subsys/bluetooth/controller/hci/nordic/hci_vendor.c#L29 - device_address[0] = NRF_FICR->DEVICEADDR[0]; - device_address[1] = NRF_FICR->DEVICEADDR[1] | 0xC000; - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 6, (uint8_t *)(&device_address[0])); + + struct { + uint16_t device_address_HSW; + uint32_t device_address_LSW; + } PACKED payload; + payload.device_address_LSW = U32HTONL(NRF_FICR->DEVICEADDR[0]); + payload.device_address_HSW = U16HTONS(NRF_FICR->DEVICEADDR[1] | 0xC000); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, sizeof(payload), (uint8_t *)&payload); } -data_frame_tx_t *cmd_processor_save_settings(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_save_settings(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { status = settings_save_config(); return data_frame_make(cmd, status, 0, NULL); } -data_frame_tx_t *cmd_processor_reset_settings(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_reset_settings(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { settings_init_config(); status = settings_save_config(); return data_frame_make(cmd, status, 0, NULL); } -data_frame_tx_t *cmd_processor_get_settings(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - uint8_t settings[7 + BLE_CONNECT_KEY_LEN_MAX] = {}; +static data_frame_tx_t *cmd_processor_get_device_settings(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + uint8_t settings[7 + BLE_PAIRING_KEY_LEN] = {}; settings[0] = SETTINGS_CURRENT_VERSION; // current version settings[1] = settings_get_animation_config(); // animation mode settings[2] = settings_get_button_press_config('A'); // short A button press mode @@ -126,357 +138,480 @@ data_frame_tx_t *cmd_processor_get_settings(uint16_t cmd, uint16_t status, uint1 settings[4] = settings_get_long_button_press_config('A'); // long A button press mode settings[5] = settings_get_long_button_press_config('B'); // long B button press mode settings[6] = settings_get_ble_pairing_enable(); // is device require pairing - memcpy(settings + 7, settings_get_ble_connect_key(), BLE_CONNECT_KEY_LEN_MAX); - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 7 + BLE_CONNECT_KEY_LEN_MAX, settings); + memcpy(settings + 7, settings_get_ble_connect_key(), BLE_PAIRING_KEY_LEN); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 7 + BLE_PAIRING_KEY_LEN, settings); } -data_frame_tx_t *cmd_processor_set_animation_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == 1) { - status = STATUS_DEVICE_SUCCESS; - settings_set_animation_config(data[0]); - } else { - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_set_animation_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if ((length != 1) || (data[0] > 2)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, 0, NULL); + settings_set_animation_config(data[0]); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_get_animation_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_get_animation_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { uint8_t animation_mode = settings_get_animation_config(); - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, (uint8_t *)(&animation_mode)); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, &animation_mode); } -data_frame_tx_t *cmd_processor_get_battery_info(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - uint8_t resp[3] = { 0x00 }; - // set voltage - num_to_bytes(batt_lvl_in_milli_volts, 2, resp); - // set percentage - resp[2] = percentage_batt_lvl; - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, sizeof(resp), resp); +static data_frame_tx_t *cmd_processor_get_battery_info(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + struct { + uint16_t voltage; + uint8_t percent; + } PACKED payload; + payload.voltage = U16HTONS(batt_lvl_in_milli_volts); + payload.percent = percentage_batt_lvl; + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, sizeof(payload), (uint8_t *)&payload); } -data_frame_tx_t *cmd_processor_get_button_press_config(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - uint8_t button_press_config; - if (length == 1 && is_settings_button_type_valid(data[0])) { - button_press_config = settings_get_button_press_config(data[0]); - status = STATUS_DEVICE_SUCCESS; - } else { - length = 0; - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_get_button_press_config(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if ((length != 1) || (!is_settings_button_type_valid(data[0]))) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, length, (uint8_t *)(&button_press_config)); + uint8_t button_press_config = settings_get_button_press_config(data[0]); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, sizeof(button_press_config), &button_press_config); } -data_frame_tx_t *cmd_processor_set_button_press_config(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == 2 && is_settings_button_type_valid(data[0])) { - settings_set_button_press_config(data[0], data[1]); - status = STATUS_DEVICE_SUCCESS; - } else { - length = 0; - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_set_button_press_config(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if ((length != 2) || (!is_settings_button_type_valid(data[0]))) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, 0, NULL); + settings_set_button_press_config(data[0], data[1]); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_get_long_button_press_config(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - uint8_t button_press_config; - if (length == 1 && is_settings_button_type_valid(data[0])) { - button_press_config = settings_get_long_button_press_config(data[0]); - status = STATUS_DEVICE_SUCCESS; - } else { - length = 0; - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_get_long_button_press_config(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if ((length != 1) || (!is_settings_button_type_valid(data[0]))) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, length, (uint8_t *)(&button_press_config)); + uint8_t button_press_config = settings_get_long_button_press_config(data[0]); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, sizeof(button_press_config), &button_press_config); } -data_frame_tx_t *cmd_processor_set_long_button_press_config(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == 2 && is_settings_button_type_valid(data[0])) { - settings_set_long_button_press_config(data[0], data[1]); - status = STATUS_DEVICE_SUCCESS; - } else { - length = 0; - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_set_long_button_press_config(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if ((length != 2) || (!is_settings_button_type_valid(data[0]))) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, 0, NULL); + settings_set_long_button_press_config(data[0], data[1]); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_get_ble_pairing_enable(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_get_ble_pairing_enable(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { uint8_t is_enable = settings_get_ble_pairing_enable(); - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, (uint8_t *)(&is_enable)); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, &is_enable); } -data_frame_tx_t *cmd_processor_set_ble_pairing_enable(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == 1 && (data[0] == true || data[0] == false)) { - settings_set_ble_pairing_enable(data[0]); - status = STATUS_DEVICE_SUCCESS; - } else { - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_set_ble_pairing_enable(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length != 1 && data[0] > 1) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, 0, NULL); + settings_set_ble_pairing_enable(data[0]); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } #if defined(PROJECT_CHAMELEON_ULTRA) -data_frame_tx_t *cmd_processor_14a_scan(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_hf14a_scan(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { picc_14a_tag_t taginfo; status = pcd_14a_reader_scan_auto(&taginfo); - if (status == HF_TAG_OK) { - length = sizeof(picc_14a_tag_t); - data = (uint8_t *)&taginfo; - } else { - length = 0; - data = NULL; + if (status != HF_TAG_OK) { + return data_frame_make(cmd, status, 0, NULL); } - return data_frame_make(cmd, status, length, data); -} - -data_frame_tx_t *cmd_processor_detect_mf1_support(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + // uidlen[1]|uid[uidlen]|atqa[2]|sak[1]|atslen[1]|ats[atslen] + // dynamic length, so no struct + uint8_t payload[1 + sizeof(taginfo.uid) + sizeof(taginfo.atqa) + sizeof(taginfo.sak) + 1 + 254]; + uint16_t offset = 0; + payload[offset++] = taginfo.uid_len; + memcpy(&payload[offset], taginfo.uid, taginfo.uid_len); + offset += taginfo.uid_len; + memcpy(&payload[offset], taginfo.atqa, sizeof(taginfo.atqa)); + offset += sizeof(taginfo.atqa); + payload[offset++] = taginfo.sak; + payload[offset++] = taginfo.ats_len; + memcpy(&payload[offset], taginfo.ats, taginfo.ats_len); + offset += taginfo.ats_len; + return data_frame_make(cmd, HF_TAG_OK, offset, payload); +} + +static data_frame_tx_t *cmd_processor_mf1_detect_support(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { status = check_std_mifare_nt_support(); return data_frame_make(cmd, status, 0, NULL); } -data_frame_tx_t *cmd_processor_detect_mf1_nt_level(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - status = check_weak_nested_support(); - return data_frame_make(cmd, status, 0, NULL); +static data_frame_tx_t *cmd_processor_mf1_detect_prng(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + uint8_t type; + status = check_prng_type((mf1_prng_type_t *)&type); + if (status != HF_TAG_OK) { + return data_frame_make(cmd, status, 0, NULL); + } + return data_frame_make(cmd, HF_TAG_OK, sizeof(type), &type); } -data_frame_tx_t *cmd_processor_detect_mf1_darkside(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - status = check_darkside_support(); - return data_frame_make(cmd, status, 0, NULL); +// We have a reusable payload structure. +typedef struct { + uint8_t type_known; + uint8_t block_known; + uint8_t key_known[6]; + uint8_t type_target; + uint8_t block_target; +} PACKED nested_common_payload_t; + +static data_frame_tx_t *cmd_processor_mf1_static_nested_acquire(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + mf1_static_nested_core_t sncs; + if (length != sizeof(nested_common_payload_t)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + } + + nested_common_payload_t *payload = (nested_common_payload_t *)data; + status = static_nested_recover_key(bytes_to_num(payload->key_known, 6), payload->block_known, payload->type_known, payload->block_target, payload->type_target, &sncs); + if (status != HF_TAG_OK) { + return data_frame_make(cmd, status, 0, NULL); + } + // mf1_static_nested_core_t is PACKED and comprises only bytes so we can use it directly + return data_frame_make(cmd, HF_TAG_OK, sizeof(sncs), (uint8_t *)(&sncs)); } -data_frame_tx_t *cmd_processor_mf1_darkside_acquire(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - DarksideCore dc; - if (length == 4) { - status = darkside_recover_key(data[1], data[0], data[2], data[3], &dc); - if (status == HF_TAG_OK) { - length = sizeof(DarksideCore); - data = (uint8_t *)(&dc); - } else { - length = 0; - } - } else { - status = STATUS_PAR_ERR; - length = 0; +static data_frame_tx_t *cmd_processor_mf1_darkside_acquire(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length != 4) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, length, data); + struct { + uint8_t darkside_status; + // DarksideCore_t is PACKED and comprises only bytes so we can use it directly + DarksideCore_t dc; + } PACKED payload; + status = darkside_recover_key(data[1], data[0], data[2], data[3], &payload.dc, (mf1_darkside_status_t *)&payload.darkside_status); + if (status != HF_TAG_OK) { + return data_frame_make(cmd, status, 0, NULL); + } + if (payload.darkside_status != DARKSIDE_OK) { + return data_frame_make(cmd, HF_TAG_OK, sizeof(payload.darkside_status), &payload.darkside_status); + } + return data_frame_make(cmd, HF_TAG_OK, sizeof(payload), (uint8_t *)&payload); } -data_frame_tx_t *cmd_processor_mf1_nt_distance(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - NestedDist nd; - if (length == 8) { - status = nested_distance_detect(data[1], data[0], &data[2], &nd); - if (status == HF_TAG_OK) { - length = sizeof(NestedDist); - data = (uint8_t *)(&nd); - } else { - length = 0; - } - } else { - status = STATUS_PAR_ERR; - length = 0; +static data_frame_tx_t *cmd_processor_mf1_detect_nt_dist(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + typedef struct { + uint8_t type_known; + uint8_t block_known; + uint8_t key_known[6]; + } PACKED payload_t; + if (length != sizeof(payload_t)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + } + + struct { + uint8_t uid[4]; + uint32_t distance; + } PACKED payload_resp; + + payload_t *payload = (payload_t *)data; + uint32_t distance; + status = nested_distance_detect(payload->block_known, payload->type_known, payload->key_known, payload_resp.uid, &distance); + if (status != HF_TAG_OK) { + return data_frame_make(cmd, status, 0, NULL); } - return data_frame_make(cmd, status, length, data); + payload_resp.distance = U32HTONL(distance); + return data_frame_make(cmd, HF_TAG_OK, sizeof(payload_resp), (uint8_t *)&payload_resp); } -data_frame_tx_t *cmd_processor_mf1_nested_acquire(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - NestedCore ncs[SETS_NR]; - if (length == 10) { - status = nested_recover_key(bytes_to_num(&data[2], 6), data[1], data[0], data[9], data[8], ncs); - if (status == HF_TAG_OK) { - length = sizeof(ncs); - data = (uint8_t *)(&ncs); - } else { - length = 0; - } - } else { - status = STATUS_PAR_ERR; - length = 0; +static data_frame_tx_t *cmd_processor_mf1_nested_acquire(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + mf1_nested_core_t ncs[SETS_NR]; + if (length != sizeof(nested_common_payload_t)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + } + + nested_common_payload_t *payload = (nested_common_payload_t *)data; + status = nested_recover_key(bytes_to_num(payload->key_known, 6), payload->block_known, payload->type_known, payload->block_target, payload->type_target, ncs); + if (status != HF_TAG_OK) { + return data_frame_make(cmd, status, 0, NULL); } - return data_frame_make(cmd, status, length, data); + // mf1_nested_core_t is PACKED and comprises only bytes so we can use it directly + return data_frame_make(cmd, HF_TAG_OK, sizeof(ncs), (uint8_t *)(&ncs)); } -data_frame_tx_t *cmd_processor_mf1_auth_one_key_block(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == 8) { - status = auth_key_use_522_hw(data[1], data[0], &data[2]); - pcd_14a_reader_mf1_unauth(); - } else { - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_mf1_auth_one_key_block(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + typedef struct { + uint8_t type; + uint8_t block; + uint8_t key[6]; + } PACKED payload_t; + if (length != sizeof(payload_t)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } + + payload_t *payload = (payload_t *)data; + status = auth_key_use_522_hw(payload->block, payload->type, payload->key); + pcd_14a_reader_mf1_unauth(); return data_frame_make(cmd, status, 0, NULL); } -data_frame_tx_t *cmd_processor_mf1_read_one_block(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_mf1_read_one_block(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + typedef struct { + uint8_t type; + uint8_t block; + uint8_t key[6]; + } PACKED payload_t; + if (length != sizeof(payload_t)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + } + + payload_t *payload = (payload_t *)data; uint8_t block[16] = { 0x00 }; - if (length == 8) { - status = auth_key_use_522_hw(data[1], data[0], &data[2]); - if (status == HF_TAG_OK) { - status = pcd_14a_reader_mf1_read(data[1], block); - if (status == HF_TAG_OK) { - length = 16; - } else { - length = 0; - } - } else { - length = 0; - } - } else { - length = 0; - status = STATUS_PAR_ERR; + status = auth_key_use_522_hw(payload->block, payload->type, payload->key); + if (status != HF_TAG_OK) { + return data_frame_make(cmd, status, 0, NULL); + } + status = pcd_14a_reader_mf1_read(payload->block, block); + if (status != HF_TAG_OK) { + return data_frame_make(cmd, status, 0, NULL); } - return data_frame_make(cmd, status, length, block); + return data_frame_make(cmd, status, sizeof(block), block); } -data_frame_tx_t *cmd_processor_mf1_write_one_block(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == 24) { - status = auth_key_use_522_hw(data[1], data[0], &data[2]); - if (status == HF_TAG_OK) { - status = pcd_14a_reader_mf1_write(data[1], &data[8]); - } else { - length = 0; - } - } else { - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_mf1_write_one_block(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + typedef struct { + uint8_t type; + uint8_t block; + uint8_t key[6]; + uint8_t block_data[16]; + } PACKED payload_t; + if (length != sizeof(payload_t)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } + + payload_t *payload = (payload_t *)data; + status = auth_key_use_522_hw(payload->block, payload->type, payload->key); + if (status != HF_TAG_OK) { + return data_frame_make(cmd, status, 0, NULL); + } + status = pcd_14a_reader_mf1_write(payload->block, payload->block_data); return data_frame_make(cmd, status, 0, NULL); } -data_frame_tx_t *cmd_processor_em410x_scan(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_hf14a_raw(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + // Response Buffer + uint8_t resp[DEF_FIFO_LENGTH] = { 0x00 }; + uint16_t resp_length = 0; + + typedef struct { + struct { // LSB -> MSB + uint8_t reserved : 2; + + uint8_t check_response_crc : 1; + uint8_t keep_rf_field : 1; + uint8_t auto_select : 1; + uint8_t append_crc : 1; + uint8_t wait_response : 1; + uint8_t activate_rf_field : 1; + } options; + + // U16NTOHS + uint16_t resp_timeout; + uint16_t data_bitlength; + + uint8_t data_buffer[0]; // We can have a lot of data or no data. struct just to compute offsets with min options. + } PACKED payload_t; + payload_t *payload = (payload_t *)data; + if (length < sizeof(payload_t)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + } + + NRF_LOG_INFO("activate_rf_field = %d", payload->options.activate_rf_field); + NRF_LOG_INFO("wait_response = %d", payload->options.wait_response); + NRF_LOG_INFO("append_crc = %d", payload->options.append_crc); + NRF_LOG_INFO("auto_select = %d", payload->options.auto_select); + NRF_LOG_INFO("keep_rf_field = %d", payload->options.keep_rf_field); + NRF_LOG_INFO("check_response_crc = %d", payload->options.check_response_crc); + NRF_LOG_INFO("reserved = %d", payload->options.reserved); + + status = pcd_14a_reader_raw_cmd( + payload->options.activate_rf_field, + payload->options.wait_response, + payload->options.append_crc, + payload->options.auto_select, + payload->options.keep_rf_field, + payload->options.check_response_crc, + + U16NTOHS(payload->resp_timeout), + + U16NTOHS(payload->data_bitlength), + payload->data_buffer, + + resp, + &resp_length, + U8ARR_BIT_LEN(resp) + ); + + return data_frame_make(cmd, status, resp_length, resp); +} + +static data_frame_tx_t *cmd_processor_em410x_scan(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { uint8_t id_buffer[5] = { 0x00 }; status = PcdScanEM410X(id_buffer); - return data_frame_make(cmd, status, sizeof(id_buffer), id_buffer); + if (status != LF_TAG_OK) { + return data_frame_make(cmd, status, 0, NULL); + } + return data_frame_make(cmd, LF_TAG_OK, sizeof(id_buffer), id_buffer); } -data_frame_tx_t *cmd_processor_write_em410x_2_t57(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length >= 13 && (length - 9) % 4 == 0) { - status = PcdWriteT55XX(data, data + 5, data + 9, (length - 9) / 4); - } else { - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_em410x_write_to_t55XX(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + typedef struct { + uint8_t id[5]; + uint8_t old_key[4]; + uint8_t new_keys[4]; // we can have more than one... struct just to compute offsets with min 1 key + } PACKED payload_t; + payload_t *payload = (payload_t *)data; + if (length < sizeof(payload_t) || (length - offsetof(payload_t, new_keys)) % sizeof(payload->new_keys) != 0) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } + + status = PcdWriteT55XX(payload->id, payload->old_key, payload->new_keys, (length - offsetof(payload_t, new_keys)) / sizeof(payload->new_keys)); return data_frame_make(cmd, status, 0, NULL); } #endif -static void change_slot_auto(uint8_t slot) { - device_mode_t mode = get_device_mode(); - tag_emulation_change_slot(slot, mode != DEVICE_MODE_READER); - light_up_by_slot(); - set_slot_light_color(0); +static data_frame_tx_t *cmd_processor_set_active_slot(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length != 1 || data[0] >= TAG_MAX_SLOT_NUM) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + } + change_slot_auto(data[0]); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_set_slot_activated(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == 1 && data[0] < TAG_MAX_SLOT_NUM) { - change_slot_auto(data[0]); - status = STATUS_DEVICE_SUCCESS; - } else { - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_set_slot_tag_type(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + typedef struct { + uint8_t num_slot; + uint16_t tag_type; + } PACKED payload_t; + if (length != sizeof(payload_t)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, 0, NULL); + + payload_t *payload = (payload_t *)data; + tag_specific_type_t tag_type = U16NTOHS(payload->tag_type); + if (payload->num_slot >= TAG_MAX_SLOT_NUM || !is_tag_specific_type_valid(tag_type)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + } + tag_emulation_change_type(payload->num_slot, tag_type); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_set_slot_tag_type(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == 2 && data[0] < TAG_MAX_SLOT_NUM && data[1] != TAG_TYPE_UNKNOWN) { - uint8_t num_slot = data[0]; - uint8_t tag_type = data[1]; - tag_emulation_change_type(num_slot, (tag_specific_type_t)tag_type); - status = STATUS_DEVICE_SUCCESS; - } else { - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_delete_slot_sense_type(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + typedef struct { + uint8_t num_slot; + uint8_t sense_type; + } PACKED payload_t; + payload_t *payload = (payload_t *)data; + if ((length != sizeof(payload_t)) || + (payload->num_slot >= TAG_MAX_SLOT_NUM) || + (payload->sense_type != TAG_SENSE_HF && payload->sense_type != TAG_SENSE_LF)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, 0, NULL); + + tag_emulation_delete_data(payload->num_slot, payload->sense_type); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_delete_slot_sense_type(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - status = STATUS_PAR_ERR; - if (length == 2 && data[0] < TAG_MAX_SLOT_NUM && (data[1] == TAG_SENSE_HF || data[1] == TAG_SENSE_LF)) { - uint8_t slot_num = data[0]; - uint8_t sense_type = data[1]; +static data_frame_tx_t *cmd_processor_set_slot_data_default(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + typedef struct { + uint8_t num_slot; + uint16_t tag_type; + } PACKED payload_t; + if (length != sizeof(payload_t)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + } - tag_emulation_delete_data(slot_num, sense_type); - status = STATUS_DEVICE_SUCCESS; + payload_t *payload = (payload_t *)data; + tag_specific_type_t tag_type = U16NTOHS(payload->tag_type); + if (payload->num_slot >= TAG_MAX_SLOT_NUM || !is_tag_specific_type_valid(tag_type)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } + status = tag_emulation_factory_data(payload->num_slot, tag_type) ? STATUS_DEVICE_SUCCESS : STATUS_NOT_IMPLEMENTED; return data_frame_make(cmd, status, 0, NULL); } -data_frame_tx_t *cmd_processor_set_slot_data_default(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == 2 && data[0] < TAG_MAX_SLOT_NUM && data[1] != TAG_TYPE_UNKNOWN) { - uint8_t num_slot = data[0]; - uint8_t tag_type = data[1]; - status = tag_emulation_factory_data(num_slot, (tag_specific_type_t)tag_type) ? STATUS_DEVICE_SUCCESS : STATUS_NOT_IMPLEMENTED; - } else { - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_set_slot_enable(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + typedef struct { + uint8_t slot_index; + uint8_t sense_type; + uint8_t enabled; + } PACKED payload_t; + + payload_t *payload = (payload_t *)data; + if (length != sizeof(payload_t) || + payload->slot_index >= TAG_MAX_SLOT_NUM || + (payload->sense_type != TAG_SENSE_HF && payload->sense_type != TAG_SENSE_LF) || + payload->enabled > 1) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, 0, NULL); -} -data_frame_tx_t *cmd_processor_set_slot_enable(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == 2 && data[0] < TAG_MAX_SLOT_NUM && (data[1] == 0 || data[1] == 1)) { - uint8_t slot_now = data[0]; - bool enable = data[1]; - tag_emulation_slot_set_enable(slot_now, enable); - if (!enable) { - uint8_t slot_prev = tag_emulation_slot_find_next(slot_now); - NRF_LOG_INFO("slot_now = %d, slot_prev = %d", slot_now, slot_prev); - if (slot_prev == slot_now) { - set_slot_light_color(3); - } else { - change_slot_auto(slot_prev); - } + uint8_t slot_now = payload->slot_index; + tag_emulation_slot_set_enable(slot_now, payload->sense_type, payload->enabled); + if ((!payload->enabled) && + (!tag_emulation_slot_is_enabled(slot_now, payload->sense_type == TAG_SENSE_HF ? TAG_SENSE_LF : TAG_SENSE_HF))) { + // HF and LF disabled, need to change slot + uint8_t slot_prev = tag_emulation_slot_find_next(slot_now); + NRF_LOG_INFO("slot_now = %d, slot_prev = %d", slot_now, slot_prev); + if (slot_prev == slot_now) { + set_slot_light_color(RGB_MAGENTA); + } else { + change_slot_auto(slot_prev); } - status = STATUS_DEVICE_SUCCESS; - } else { - status = STATUS_PAR_ERR; } - return data_frame_make(cmd, status, 0, NULL); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_slot_data_config_save(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_slot_data_config_save(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { tag_emulation_save(); return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_get_activated_slot(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_get_active_slot(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { uint8_t slot = tag_emulation_get_slot(); return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, &slot); } -data_frame_tx_t *cmd_processor_get_slot_info(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - uint8_t slot_info[16] = {}; - tag_specific_type_t tag_type[2]; +static data_frame_tx_t *cmd_processor_get_slot_info(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + struct { + uint16_t hf_tag_type; + uint16_t lf_tag_type; + } PACKED payload[8]; + + tag_slot_specific_type_t tag_types; for (uint8_t slot = 0; slot < 8; slot++) { - tag_emulation_get_specific_type_by_slot(slot, tag_type); - slot_info[slot * 2] = tag_type[0]; - slot_info[slot * 2 + 1] = tag_type[1]; + tag_emulation_get_specific_types_by_slot(slot, &tag_types); + payload[slot].hf_tag_type = U16HTONS(tag_types.tag_hf); + payload[slot].lf_tag_type = U16HTONS(tag_types.tag_lf); } - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 16, slot_info); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, sizeof(payload), (uint8_t *)&payload); } -data_frame_tx_t *cmd_processor_wipe_fds(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_wipe_fds(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { bool success = fds_wipe(); status = success ? STATUS_DEVICE_SUCCESS : STATUS_FLASH_WRITE_FAIL; delayed_reset(50); return data_frame_make(cmd, status, 0, NULL); } -data_frame_tx_t *cmd_processor_set_em410x_emu_id(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == LF_EM410X_TAG_ID_SIZE) { - tag_data_buffer_t *buffer = get_buffer_by_tag_type(TAG_TYPE_EM410X); - memcpy(buffer->buffer, data, LF_EM410X_TAG_ID_SIZE); - tag_emulation_load_by_buffer(TAG_TYPE_EM410X, false); - status = STATUS_DEVICE_SUCCESS; - } else { - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_em410x_set_emu_id(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length != LF_EM410X_TAG_ID_SIZE) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, 0, NULL); + tag_data_buffer_t *buffer = get_buffer_by_tag_type(TAG_TYPE_EM410X); + memcpy(buffer->buffer, data, LF_EM410X_TAG_ID_SIZE); + tag_emulation_load_by_buffer(TAG_TYPE_EM410X, false); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_get_em410x_emu_id(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - tag_specific_type_t tag_type[2]; - tag_emulation_get_specific_type_by_slot(tag_emulation_get_slot(), tag_type); - if (tag_type[1] == TAG_TYPE_UNKNOWN) { +static data_frame_tx_t *cmd_processor_em410x_get_emu_id(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + tag_slot_specific_type_t tag_types; + tag_emulation_get_specific_types_by_slot(tag_emulation_get_slot(), &tag_types); + if (tag_types.tag_lf != TAG_TYPE_EM410X) { return data_frame_make(cmd, STATUS_PAR_ERR, 0, data); // no data in slot, don't send garbage } tag_data_buffer_t *buffer = get_buffer_by_tag_type(TAG_TYPE_EM410X); @@ -485,348 +620,277 @@ data_frame_tx_t *cmd_processor_get_em410x_emu_id(uint16_t cmd, uint16_t status, return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, LF_EM410X_TAG_ID_SIZE, responseData); } -data_frame_tx_t *cmd_processor_get_mf1_anti_coll_data(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - tag_specific_type_t tag_type[2]; - tag_emulation_get_specific_type_by_slot(tag_emulation_get_slot(), tag_type); - if (tag_type[0] == TAG_TYPE_UNKNOWN) { - return data_frame_make(cmd, STATUS_PAR_ERR, 0, data); // no data in slot, don't send garbage +static data_frame_tx_t *cmd_processor_hf14a_get_anti_coll_data(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + tag_slot_specific_type_t tag_types; + tag_emulation_get_specific_types_by_slot(tag_emulation_get_slot(), &tag_types); + if (tag_types.tag_hf == TAG_TYPE_UNDEFINED) { + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); // no data in slot, don't send garbage } - uint8_t responseData[16] = {}; nfc_tag_14a_coll_res_reference_t *info = get_saved_mifare_coll_res(); - memcpy(responseData, info->uid, *info->size); - responseData[10] = *info->size; // size is 2 byte len, but... - responseData[12] = info->sak[0]; - memcpy(&responseData[13], info->atqa, 2); - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 16, responseData); -} - -data_frame_tx_t *cmd_processor_set_mf1_detection_enable(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == 1 && (data[0] == 0 || data[0] == 1)) { - nfc_tag_mf1_detection_log_clear(); - nfc_tag_mf1_set_detection_enable(data[0]); - status = STATUS_DEVICE_SUCCESS; + // uidlen[1]|uid[uidlen]|atqa[2]|sak[1]|atslen[1]|ats[atslen] + // dynamic length, so no struct + uint8_t payload[1 + *info->size + 2 + 1 + 1 + 254]; + uint16_t offset = 0; + payload[offset++] = *info->size; + memcpy(&payload[offset], info->uid, *info->size); + offset += *info->size; + memcpy(&payload[offset], info->atqa, 2); + offset += 2; + payload[offset++] = *info->sak; + if (info->ats->length > 0) { + payload[offset++] = info->ats->length; + memcpy(&payload[offset], info->ats->data, info->ats->length); + offset += info->ats->length; } else { - status = STATUS_PAR_ERR; + payload[offset++] = 0; } - return data_frame_make(cmd, status, 0, NULL); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, offset, payload); } -data_frame_tx_t *cmd_processor_get_mf1_detection_status(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (nfc_tag_mf1_is_detection_enable()) { - status = 1; - } else { - status = 0; +static data_frame_tx_t *cmd_processor_mf1_set_detection_enable(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length != 1 || data[0] > 1) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, (uint8_t *)&status); + nfc_tag_mf1_detection_log_clear(); + nfc_tag_mf1_set_detection_enable(data[0]); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_get_mf1_detection_count(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_mf1_get_detection_enable(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + uint8_t is_enable = nfc_tag_mf1_is_detection_enable(); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, (uint8_t *)(&is_enable)); +} + +static data_frame_tx_t *cmd_processor_mf1_get_detection_count(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { uint32_t count = nfc_tag_mf1_detection_log_count(); if (count == 0xFFFFFFFF) { count = 0; } - status = STATUS_DEVICE_SUCCESS; - return data_frame_make(cmd, status, sizeof(uint32_t), (uint8_t *)&count); + uint32_t payload = U32HTONL(count); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, sizeof(uint32_t), (uint8_t *)&payload); } -data_frame_tx_t *cmd_processor_get_mf1_detection_log(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_mf1_get_detection_log(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { uint32_t count; uint32_t index; uint8_t *resp = NULL; - nfc_tag_mf1_auth_log_t *logs = get_mf1_auth_log(&count); - if (length == 4) { - if (count == 0xFFFFFFFF) { - length = 0; - status = STATUS_PAR_ERR; - } else { - index = bytes_to_num(data, 4); - // NRF_LOG_INFO("index = %d", index); - if (index < count) { - resp = (uint8_t *)(logs + index); - length = MIN(count - index, DATA_PACK_MAX_DATA_LENGTH / sizeof(nfc_tag_mf1_auth_log_t)); - length = length * sizeof(nfc_tag_mf1_auth_log_t); - status = STATUS_DEVICE_SUCCESS; - } else { - length = 0; - status = STATUS_PAR_ERR; - } - } - } else { - length = 0; - status = STATUS_PAR_ERR; + nfc_tag_mf1_auth_log_t *logs = mf1_get_auth_log(&count); + if (length != 4 || count == 0xFFFFFFFF) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, length, resp); + index = U32NTOHL(*(uint32_t *)data); + // NRF_LOG_INFO("index = %d", index); + if (index >= count) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + } + resp = (uint8_t *)(logs + index); + length = MIN(count - index, NETDATA_MAX_DATA_LENGTH / sizeof(nfc_tag_mf1_auth_log_t)) * sizeof(nfc_tag_mf1_auth_log_t); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, length, resp); } -data_frame_tx_t *cmd_processor_set_mf1_emulator_block(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length > 0 && (((length - 1) % NFC_TAG_MF1_DATA_SIZE) == 0)) { - uint8_t block_index = data[0]; - uint8_t block_count = (length - 1) / NFC_TAG_MF1_DATA_SIZE; - if (block_index + block_count > NFC_TAG_MF1_BLOCK_MAX) { - status = STATUS_PAR_ERR; - } else { - tag_data_buffer_t *buffer = get_buffer_by_tag_type(TAG_TYPE_MIFARE_4096); - nfc_tag_mf1_information_t *info = (nfc_tag_mf1_information_t *)buffer->buffer; - for (int i = 1, j = block_index; i < length; i += NFC_TAG_MF1_DATA_SIZE, j++) { - uint8_t *p_block = &data[i]; - memcpy(info->memory[j], p_block, NFC_TAG_MF1_DATA_SIZE); - } - status = STATUS_DEVICE_SUCCESS; - } - } else { +static data_frame_tx_t *cmd_processor_mf1_write_emu_block_data(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length == 0 || (((length - 1) % NFC_TAG_MF1_DATA_SIZE) != 0)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + } + uint8_t block_index = data[0]; + uint8_t block_count = (length - 1) / NFC_TAG_MF1_DATA_SIZE; + if (block_index + block_count > NFC_TAG_MF1_BLOCK_MAX) { status = STATUS_PAR_ERR; } - return data_frame_make(cmd, status, 0, NULL); + tag_data_buffer_t *buffer = get_buffer_by_tag_type(TAG_TYPE_MIFARE_4096); + nfc_tag_mf1_information_t *info = (nfc_tag_mf1_information_t *)buffer->buffer; + for (int i = 1, j = block_index; i < length; i += NFC_TAG_MF1_DATA_SIZE, j++) { + uint8_t *p_block = &data[i]; + memcpy(info->memory[j], p_block, NFC_TAG_MF1_DATA_SIZE); + } + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_get_mf1_emulator_block(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == 2) { - uint8_t block_index = data[0]; - uint8_t block_count = data[1]; - - // block_count > 32 will overflow the maximum message size - if (block_count != 0 && block_count <= 32 && (uint16_t) block_index + block_count < NFC_TAG_MF1_BLOCK_MAX) { - tag_data_buffer_t *buffer = get_buffer_by_tag_type(TAG_TYPE_MIFARE_4096); - nfc_tag_mf1_information_t *info = (nfc_tag_mf1_information_t *)buffer->buffer; - uint16_t result_length = block_count * NFC_TAG_MF1_DATA_SIZE; - uint8_t result_buffer[result_length]; - for (int i = 0, j = block_index; i < result_length; i += NFC_TAG_MF1_DATA_SIZE, j++) { - uint8_t *p_block = &result_buffer[i]; - memcpy(p_block, info->memory[j], NFC_TAG_MF1_DATA_SIZE); - } - - return data_frame_make(cmd, status, result_length, result_buffer); - } +static data_frame_tx_t *cmd_processor_mf1_read_emu_block_data(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if ((length != 2) || (data[1] < 1) || (data[1] > 32) || (data[0] + data[1] > NFC_TAG_MF1_BLOCK_MAX)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - - return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + uint8_t block_index = data[0]; + uint8_t block_count = data[1]; + tag_data_buffer_t *buffer = get_buffer_by_tag_type(TAG_TYPE_MIFARE_4096); + nfc_tag_mf1_information_t *info = (nfc_tag_mf1_information_t *)buffer->buffer; + uint16_t result_length = block_count * NFC_TAG_MF1_DATA_SIZE; + uint8_t result_buffer[result_length]; + for (int i = 0, j = block_index; i < result_length; i += NFC_TAG_MF1_DATA_SIZE, j++) { + memcpy(&result_buffer[i], info->memory[j], NFC_TAG_MF1_DATA_SIZE); + } + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, result_length, result_buffer); } -data_frame_tx_t *cmd_processor_set_mf1_anti_collision_res(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length > 13) { - // sak(1) + atqa(2) + uid(10) - status = STATUS_PAR_ERR; - } else { - uint8_t uid_length = length - 3; - if (is_valid_uid_size(uid_length)) { - nfc_tag_14a_coll_res_reference_t *info = get_mifare_coll_res(); - // copy sak - info->sak[0] = data[0]; - // copy atqa - memcpy(info->atqa, &data[1], 2); - // copy uid - memcpy(info->uid, &data[3], uid_length); - // copy size - *(info->size) = (nfc_tag_14a_uid_size)uid_length; - status = STATUS_DEVICE_SUCCESS; - } else { - status = STATUS_PAR_ERR; - } +static data_frame_tx_t *cmd_processor_hf14a_set_anti_coll_data(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + // uidlen[1]|uid[uidlen]|atqa[2]|sak[1]|atslen[1]|ats[atslen] + // dynamic length, so no struct + if ((length < 1) || \ + (!is_valid_uid_size(data[0])) || \ + (length < 1 + data[0] + 2 + 1 + 1) || \ + (length < 1 + data[0] + 2 + 1 + 1 + data[1 + data[0] + 2 + 1])) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, 0, NULL); + nfc_tag_14a_coll_res_reference_t *info = get_mifare_coll_res(); + uint16_t offset = 0; + *(info->size) = (nfc_tag_14a_uid_size)data[offset]; + offset++; + memcpy(info->uid, &data[offset], *(info->size)); + offset += *(info->size); + memcpy(info->atqa, &data[offset], 2); + offset += 2; + info->sak[0] = data[offset]; + offset ++; + info->ats->length = data[offset]; + offset ++; + memcpy(info->ats->data, &data[offset], info->ats->length); + offset += info->ats->length; + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_set_slot_tag_nick_name(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length > 34 || length < 3) { - status = STATUS_PAR_ERR; - } else { - uint8_t slot = data[0]; - uint8_t sense_type = data[1]; - fds_slot_record_map_t map_info; - - get_fds_map_by_slot_sense_type_for_nick(slot, sense_type, &map_info); +static data_frame_tx_t *cmd_processor_set_slot_tag_nick(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length < 3 || length > 34) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + } + uint8_t slot = data[0]; + uint8_t sense_type = data[1]; + fds_slot_record_map_t map_info; + if (slot >= TAG_MAX_SLOT_NUM || (sense_type != TAG_SENSE_HF && sense_type != TAG_SENSE_LF)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + } + get_fds_map_by_slot_sense_type_for_nick(slot, sense_type, &map_info); - uint8_t buffer[36]; - buffer[0] = length - 2; - memcpy(buffer + 1, data + 2, buffer[0]); + uint8_t buffer[36]; + buffer[0] = length - 2; + memcpy(buffer + 1, data + 2, buffer[0]); - bool ret = fds_write_sync(map_info.id, map_info.key, sizeof(buffer) / 4, buffer); - if (ret) { - status = STATUS_DEVICE_SUCCESS; - } else { - status = STATUS_FLASH_WRITE_FAIL; - } + bool ret = fds_write_sync(map_info.id, map_info.key, sizeof(buffer) / 4, buffer); + if (!ret) { + return data_frame_make(cmd, STATUS_FLASH_WRITE_FAIL, 0, NULL); } - return data_frame_make(cmd, status, 0, NULL); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_get_slot_tag_nick_name(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_get_slot_tag_nick(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { if (length != 2) { - status = STATUS_PAR_ERR; - return data_frame_make(cmd, status, 0, NULL); - } else { - uint8_t buffer[36]; - uint8_t slot = data[0]; - uint8_t sense_type = data[1]; - fds_slot_record_map_t map_info; - - get_fds_map_by_slot_sense_type_for_nick(slot, sense_type, &map_info); - bool ret = fds_read_sync(map_info.id, map_info.key, sizeof(buffer), buffer); - if (ret) { - status = STATUS_DEVICE_SUCCESS; - length = buffer[0]; - data = &buffer[1]; - } else { - status = STATUS_FLASH_READ_FAIL; - length = 0; - data = NULL; - } - // must be called within stack allocation of buffer - return data_frame_make(cmd, status, length, data); + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + } + uint8_t slot = data[0]; + uint8_t sense_type = data[1]; + uint8_t buffer[36]; + fds_slot_record_map_t map_info; + + if (slot >= TAG_MAX_SLOT_NUM || (sense_type != TAG_SENSE_HF && sense_type != TAG_SENSE_LF)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + } + get_fds_map_by_slot_sense_type_for_nick(slot, sense_type, &map_info); + uint16_t buffer_length = sizeof(buffer); + bool ret = fds_read_sync(map_info.id, map_info.key, &buffer_length, buffer); + if (!ret) { + return data_frame_make(cmd, STATUS_FLASH_READ_FAIL, 0, NULL); } + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, buffer[0], &buffer[1]); } -data_frame_tx_t *cmd_processor_get_mf1_info(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_mf1_get_emulator_config(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { uint8_t mf1_info[5] = {}; mf1_info[0] = nfc_tag_mf1_is_detection_enable(); mf1_info[1] = nfc_tag_mf1_is_gen1a_magic_mode(); mf1_info[2] = nfc_tag_mf1_is_gen2_magic_mode(); mf1_info[3] = nfc_tag_mf1_is_use_mf1_coll_res(); - nfc_tag_mf1_write_mode_t write_mode = nfc_tag_mf1_get_write_mode(); - if (write_mode == NFC_TAG_MF1_WRITE_NORMAL) { - mf1_info[4] = 0; - } else if (write_mode == NFC_TAG_MF1_WRITE_DENIED) { - mf1_info[4] = 1; - } else if (write_mode == NFC_TAG_MF1_WRITE_DECEIVE) { - mf1_info[4] = 2; - } else if (write_mode == NFC_TAG_MF1_WRITE_SHADOW) { - mf1_info[4] = 3; - } + mf1_info[4] = nfc_tag_mf1_get_write_mode(); return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 5, mf1_info); } -data_frame_tx_t *cmd_processor_get_mf1_gen1a_magic_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (nfc_tag_mf1_is_gen1a_magic_mode()) { - status = 1; - } else { - status = 0; - } - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, (uint8_t *)&status); +static data_frame_tx_t *cmd_processor_mf1_get_gen1a_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + uint8_t mode = nfc_tag_mf1_is_gen1a_magic_mode(); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, &mode); } -data_frame_tx_t *cmd_processor_set_mf1_gen1a_magic_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == 1 && (data[0] == 0 || data[0] == 1)) { - nfc_tag_mf1_set_gen1a_magic_mode(data[0]); - status = STATUS_DEVICE_SUCCESS; - } else { - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_mf1_set_gen1a_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length != 1 && data[0] > 1) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, 0, NULL); + nfc_tag_mf1_set_gen1a_magic_mode(data[0]); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_get_mf1_gen2_magic_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (nfc_tag_mf1_is_gen2_magic_mode()) { - status = 1; - } else { - status = 0; - } - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, (uint8_t *)&status); +static data_frame_tx_t *cmd_processor_mf1_get_gen2_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + uint8_t mode = nfc_tag_mf1_is_gen2_magic_mode(); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, &mode); } -data_frame_tx_t *cmd_processor_set_mf1_gen2_magic_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == 1 && (data[0] == 0 || data[0] == 1)) { - nfc_tag_mf1_set_gen2_magic_mode(data[0]); - status = STATUS_DEVICE_SUCCESS; - } else { - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_mf1_set_gen2_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length != 1 && data[0] > 1) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, 0, NULL); + nfc_tag_mf1_set_gen2_magic_mode(data[0]); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_get_mf1_use_coll_res(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (nfc_tag_mf1_is_use_mf1_coll_res()) { - status = 1; - } else { - status = 0; - } - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, (uint8_t *)&status); +static data_frame_tx_t *cmd_processor_mf1_get_block_anti_coll_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + uint8_t mode = nfc_tag_mf1_is_use_mf1_coll_res(); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, &mode); } -data_frame_tx_t *cmd_processor_set_mf1_use_coll_res(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == 1 && (data[0] == 0 || data[0] == 1)) { - nfc_tag_mf1_set_use_mf1_coll_res(data[0]); - status = STATUS_DEVICE_SUCCESS; - } else { - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_mf1_set_block_anti_coll_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length != 1 && data[0] > 1) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, 0, NULL); + nfc_tag_mf1_set_use_mf1_coll_res(data[0]); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_get_mf1_write_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - nfc_tag_mf1_write_mode_t write_mode = nfc_tag_mf1_get_write_mode(); - if (write_mode == NFC_TAG_MF1_WRITE_NORMAL) { - status = 0; - } else if (write_mode == NFC_TAG_MF1_WRITE_DENIED) { - status = 1; - } else if (write_mode == NFC_TAG_MF1_WRITE_DECEIVE) { - status = 2; - } else if (write_mode == NFC_TAG_MF1_WRITE_SHADOW) { - status = 3; - } - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, (uint8_t *)&status); -} - -data_frame_tx_t *cmd_processor_set_mf1_write_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == 1 && (data[0] >= 0 || data[0] <= 3)) { - uint8_t mode = data[0]; - if (mode == 0) { - nfc_tag_mf1_set_write_mode(NFC_TAG_MF1_WRITE_NORMAL); - } else if (mode == 1) { - nfc_tag_mf1_set_write_mode(NFC_TAG_MF1_WRITE_DENIED); - } else if (mode == 2) { - nfc_tag_mf1_set_write_mode(NFC_TAG_MF1_WRITE_DECEIVE); - } else if (mode == 3) { - nfc_tag_mf1_set_write_mode(NFC_TAG_MF1_WRITE_SHADOW); - } - status = STATUS_DEVICE_SUCCESS; - } else { - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_mf1_get_write_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + uint8_t mode = nfc_tag_mf1_get_write_mode(); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, &mode); +} + +static data_frame_tx_t *cmd_processor_mf1_set_write_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length != 1 || data[0] > 3) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, 0, NULL); + nfc_tag_mf1_set_write_mode(data[0]); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_get_enabled_slots(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - uint8_t slot_info[8] = {}; +static data_frame_tx_t *cmd_processor_get_enabled_slots(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + struct { + uint8_t enabled_hf; + uint8_t enabled_lf; + } PACKED payload[8]; for (uint8_t slot = 0; slot < 8; slot++) { - slot_info[slot] = tag_emulation_slot_is_enable(slot); + payload[slot].enabled_hf = tag_emulation_slot_is_enabled(slot, TAG_SENSE_HF); + payload[slot].enabled_lf = tag_emulation_slot_is_enabled(slot, TAG_SENSE_LF); } - - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 8, slot_info); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, sizeof(payload), (uint8_t *)&payload); } -data_frame_tx_t *cmd_processor_get_ble_connect_key(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - return data_frame_make( - cmd, - STATUS_DEVICE_SUCCESS, - BLE_CONNECT_KEY_LEN_MAX, // 6 - settings_get_ble_connect_key() // Get key point from config - ); +static data_frame_tx_t *cmd_processor_get_ble_connect_key(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, BLE_PAIRING_KEY_LEN, settings_get_ble_connect_key()); } -data_frame_tx_t *cmd_processor_set_ble_connect_key(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == BLE_CONNECT_KEY_LEN_MAX) { - // Must be 6 ASCII characters, can only be 0-9. - bool is_valid_key = true; - for (uint8_t i = 0; i < BLE_CONNECT_KEY_LEN_MAX; i++) { - if (data[i] < 48 || data[i] > 57) { - is_valid_key = false; - break; - } - } - if (is_valid_key) { - // Key is valid, we can update to config - settings_set_ble_connect_key(data); - status = STATUS_DEVICE_SUCCESS; - } else { - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_set_ble_connect_key(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length != BLE_PAIRING_KEY_LEN) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + } + // Must be 6 ASCII characters, can only be 0-9. + bool is_valid_key = true; + for (uint8_t i = 0; i < BLE_PAIRING_KEY_LEN; i++) { + if (data[i] < '0' || data[i] > '9') { + is_valid_key = false; + break; } - } else { - status = STATUS_PAR_ERR; } - return data_frame_make(cmd, status, 0, NULL); + if (!is_valid_key) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + } + // Key is valid, we can update to config + settings_set_ble_connect_key(data); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_del_ble_all_bonds(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_delete_all_ble_bonds(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { advertising_stop(); delete_bonds_all(); return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); @@ -839,13 +903,12 @@ data_frame_tx_t *cmd_processor_del_ble_all_bonds(uint16_t cmd, uint16_t status, * before reader run, reset reader and on antenna, * we must to wait some time, to init picc(power). */ -data_frame_tx_t *before_reader_run(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *before_reader_run(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { device_mode_t mode = get_device_mode(); - if (mode == DEVICE_MODE_READER) { - return NULL; - } else { + if (mode != DEVICE_MODE_READER) { return data_frame_make(cmd, STATUS_DEVICE_MODE_ERROR, 0, NULL); } + return NULL; } @@ -853,7 +916,7 @@ data_frame_tx_t *before_reader_run(uint16_t cmd, uint16_t status, uint16_t lengt * before reader run, reset reader and on antenna, * we must to wait some time, to init picc(power). */ -data_frame_tx_t *before_hf_reader_run(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *before_hf_reader_run(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { data_frame_tx_t *ret = before_reader_run(cmd, status, length, data); if (ret == NULL) { pcd_14a_reader_reset(); @@ -866,21 +929,31 @@ data_frame_tx_t *before_hf_reader_run(uint16_t cmd, uint16_t status, uint16_t le /** * after reader run, off antenna, to keep battery. */ -data_frame_tx_t *after_hf_reader_run(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *after_hf_reader_run(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { pcd_14a_reader_antenna_off(); return NULL; } #endif +// fct will be defined after m_data_cmd_map because we need to know its size +data_frame_tx_t *cmd_processor_get_device_capabilities(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data); + /** * (cmd -> processor) function map, the map struct is: * cmd code before process cmd processor after process */ static cmd_data_map_t m_data_cmd_map[] = { - { DATA_CMD_GET_APP_VERSION, NULL, cmd_processor_get_version, NULL }, + { DATA_CMD_GET_APP_VERSION, NULL, cmd_processor_get_app_version, NULL }, { DATA_CMD_CHANGE_DEVICE_MODE, NULL, cmd_processor_change_device_mode, NULL }, { DATA_CMD_GET_DEVICE_MODE, NULL, cmd_processor_get_device_mode, NULL }, + { DATA_CMD_SET_ACTIVE_SLOT, NULL, cmd_processor_set_active_slot, NULL }, + { DATA_CMD_SET_SLOT_TAG_TYPE, NULL, cmd_processor_set_slot_tag_type, NULL }, + { DATA_CMD_SET_SLOT_DATA_DEFAULT, NULL, cmd_processor_set_slot_data_default, NULL }, + { DATA_CMD_SET_SLOT_ENABLE, NULL, cmd_processor_set_slot_enable, NULL }, + { DATA_CMD_SET_SLOT_TAG_NICK, NULL, cmd_processor_set_slot_tag_nick, NULL }, + { DATA_CMD_GET_SLOT_TAG_NICK, NULL, cmd_processor_get_slot_tag_nick, NULL }, + { DATA_CMD_SLOT_DATA_CONFIG_SAVE, NULL, cmd_processor_slot_data_config_save, NULL }, { DATA_CMD_ENTER_BOOTLOADER, NULL, cmd_processor_enter_bootloader, NULL }, { DATA_CMD_GET_DEVICE_CHIP_ID, NULL, cmd_processor_get_device_chip_id, NULL }, { DATA_CMD_GET_DEVICE_ADDRESS, NULL, cmd_processor_get_device_address, NULL }, @@ -889,101 +962,78 @@ static cmd_data_map_t m_data_cmd_map[] = { { DATA_CMD_SET_ANIMATION_MODE, NULL, cmd_processor_set_animation_mode, NULL }, { DATA_CMD_GET_ANIMATION_MODE, NULL, cmd_processor_get_animation_mode, NULL }, { DATA_CMD_GET_GIT_VERSION, NULL, cmd_processor_get_git_version, NULL }, + { DATA_CMD_GET_ACTIVE_SLOT, NULL, cmd_processor_get_active_slot, NULL }, + { DATA_CMD_GET_SLOT_INFO, NULL, cmd_processor_get_slot_info, NULL }, + { DATA_CMD_WIPE_FDS, NULL, cmd_processor_wipe_fds, NULL }, + { DATA_CMD_GET_ENABLED_SLOTS, NULL, cmd_processor_get_enabled_slots, NULL }, + { DATA_CMD_DELETE_SLOT_SENSE_TYPE, NULL, cmd_processor_delete_slot_sense_type, NULL }, { DATA_CMD_GET_BATTERY_INFO, NULL, cmd_processor_get_battery_info, NULL }, { DATA_CMD_GET_BUTTON_PRESS_CONFIG, NULL, cmd_processor_get_button_press_config, NULL }, { DATA_CMD_SET_BUTTON_PRESS_CONFIG, NULL, cmd_processor_set_button_press_config, NULL }, { DATA_CMD_GET_LONG_BUTTON_PRESS_CONFIG, NULL, cmd_processor_get_long_button_press_config, NULL }, { DATA_CMD_SET_LONG_BUTTON_PRESS_CONFIG, NULL, cmd_processor_set_long_button_press_config, NULL }, - { DATA_CMD_GET_BLE_CONNECT_KEY_CONFIG, NULL, cmd_processor_get_ble_connect_key, NULL }, - { DATA_CMD_SET_BLE_CONNECT_KEY_CONFIG, NULL, cmd_processor_set_ble_connect_key, NULL }, - { DATA_CMD_DELETE_ALL_BLE_BONDS, NULL, cmd_processor_del_ble_all_bonds, NULL }, - { DATA_CMD_GET_DEVICE, NULL, cmd_processor_get_device, NULL }, - { DATA_CMD_GET_SETTINGS, NULL, cmd_processor_get_settings, NULL }, - { DATA_CMD_GET_DEVICE_CAPABILITIES, NULL, NULL, NULL }, + { DATA_CMD_GET_BLE_PAIRING_KEY, NULL, cmd_processor_get_ble_connect_key, NULL }, + { DATA_CMD_SET_BLE_PAIRING_KEY, NULL, cmd_processor_set_ble_connect_key, NULL }, + { DATA_CMD_DELETE_ALL_BLE_BONDS, NULL, cmd_processor_delete_all_ble_bonds, NULL }, + { DATA_CMD_GET_DEVICE_MODEL, NULL, cmd_processor_get_device_model, NULL }, + { DATA_CMD_GET_DEVICE_SETTINGS, NULL, cmd_processor_get_device_settings, NULL }, + { DATA_CMD_GET_DEVICE_CAPABILITIES, NULL, cmd_processor_get_device_capabilities, NULL }, { DATA_CMD_GET_BLE_PAIRING_ENABLE, NULL, cmd_processor_get_ble_pairing_enable, NULL }, { DATA_CMD_SET_BLE_PAIRING_ENABLE, NULL, cmd_processor_set_ble_pairing_enable, NULL }, #if defined(PROJECT_CHAMELEON_ULTRA) - { DATA_CMD_SCAN_14A_TAG, before_hf_reader_run, cmd_processor_14a_scan, after_hf_reader_run }, - { DATA_CMD_MF1_SUPPORT_DETECT, before_hf_reader_run, cmd_processor_detect_mf1_support, after_hf_reader_run }, - { DATA_CMD_MF1_NT_LEVEL_DETECT, before_hf_reader_run, cmd_processor_detect_mf1_nt_level, after_hf_reader_run }, - { DATA_CMD_MF1_DARKSIDE_DETECT, before_hf_reader_run, cmd_processor_detect_mf1_darkside, after_hf_reader_run }, - + { DATA_CMD_HF14A_SCAN, before_hf_reader_run, cmd_processor_hf14a_scan, after_hf_reader_run }, + { DATA_CMD_MF1_DETECT_SUPPORT, before_hf_reader_run, cmd_processor_mf1_detect_support, after_hf_reader_run }, + { DATA_CMD_MF1_DETECT_PRNG, before_hf_reader_run, cmd_processor_mf1_detect_prng, after_hf_reader_run }, + { DATA_CMD_MF1_STATIC_NESTED_ACQUIRE, before_hf_reader_run, cmd_processor_mf1_static_nested_acquire, after_hf_reader_run }, { DATA_CMD_MF1_DARKSIDE_ACQUIRE, before_hf_reader_run, cmd_processor_mf1_darkside_acquire, after_hf_reader_run }, - { DATA_CMD_MF1_NT_DIST_DETECT, before_hf_reader_run, cmd_processor_mf1_nt_distance, after_hf_reader_run }, + { DATA_CMD_MF1_DETECT_NT_DIST, before_hf_reader_run, cmd_processor_mf1_detect_nt_dist, after_hf_reader_run }, { DATA_CMD_MF1_NESTED_ACQUIRE, before_hf_reader_run, cmd_processor_mf1_nested_acquire, after_hf_reader_run }, - { DATA_CMD_MF1_CHECK_ONE_KEY_BLOCK, before_hf_reader_run, cmd_processor_mf1_auth_one_key_block, after_hf_reader_run }, + { DATA_CMD_MF1_AUTH_ONE_KEY_BLOCK, before_hf_reader_run, cmd_processor_mf1_auth_one_key_block, after_hf_reader_run }, { DATA_CMD_MF1_READ_ONE_BLOCK, before_hf_reader_run, cmd_processor_mf1_read_one_block, after_hf_reader_run }, { DATA_CMD_MF1_WRITE_ONE_BLOCK, before_hf_reader_run, cmd_processor_mf1_write_one_block, after_hf_reader_run }, + { DATA_CMD_HF14A_RAW, before_reader_run, cmd_processor_hf14a_raw, NULL }, - { DATA_CMD_SCAN_EM410X_TAG, before_reader_run, cmd_processor_em410x_scan, NULL }, - { DATA_CMD_WRITE_EM410X_TO_T5577, before_reader_run, cmd_processor_write_em410x_2_t57, NULL }, + { DATA_CMD_EM410X_SCAN, before_reader_run, cmd_processor_em410x_scan, NULL }, + { DATA_CMD_EM410X_WRITE_TO_T55XX, before_reader_run, cmd_processor_em410x_write_to_t55XX, NULL }, #endif - { DATA_CMD_SET_SLOT_ACTIVATED, NULL, cmd_processor_set_slot_activated, NULL }, - { DATA_CMD_SET_SLOT_TAG_TYPE, NULL, cmd_processor_set_slot_tag_type, NULL }, - { DATA_CMD_SET_SLOT_DATA_DEFAULT, NULL, cmd_processor_set_slot_data_default, NULL }, - { DATA_CMD_SET_SLOT_ENABLE, NULL, cmd_processor_set_slot_enable, NULL }, - { DATA_CMD_SLOT_DATA_CONFIG_SAVE, NULL, cmd_processor_slot_data_config_save, NULL }, - { DATA_CMD_GET_ACTIVE_SLOT, NULL, cmd_processor_get_activated_slot, NULL }, - { DATA_CMD_GET_SLOT_INFO, NULL, cmd_processor_get_slot_info, NULL }, - { DATA_CMD_WIPE_FDS, NULL, cmd_processor_wipe_fds, NULL }, - { DATA_CMD_GET_ENABLED_SLOTS, NULL, cmd_processor_get_enabled_slots, NULL }, - { DATA_CMD_DELETE_SLOT_SENSE_TYPE, NULL, cmd_processor_delete_slot_sense_type, NULL }, - - - - { DATA_CMD_SET_EM410X_EMU_ID, NULL, cmd_processor_set_em410x_emu_id, NULL }, - { DATA_CMD_GET_EM410X_EMU_ID, NULL, cmd_processor_get_em410x_emu_id, NULL }, - - { DATA_CMD_GET_MF1_DETECTION_STATUS, NULL, cmd_processor_get_mf1_detection_status, NULL }, - { DATA_CMD_SET_MF1_DETECTION_ENABLE, NULL, cmd_processor_set_mf1_detection_enable, NULL }, - { DATA_CMD_GET_MF1_DETECTION_COUNT, NULL, cmd_processor_get_mf1_detection_count, NULL }, - { DATA_CMD_GET_MF1_DETECTION_RESULT, NULL, cmd_processor_get_mf1_detection_log, NULL }, - { DATA_CMD_LOAD_MF1_EMU_BLOCK_DATA, NULL, cmd_processor_set_mf1_emulator_block, NULL }, - { DATA_CMD_READ_MF1_EMU_BLOCK_DATA, NULL, cmd_processor_get_mf1_emulator_block, NULL }, - { DATA_CMD_SET_MF1_ANTI_COLLISION_RES, NULL, cmd_processor_set_mf1_anti_collision_res, NULL }, - { DATA_CMD_GET_MF1_EMULATOR_CONFIG, NULL, cmd_processor_get_mf1_info, NULL }, - { DATA_CMD_GET_MF1_GEN1A_MODE, NULL, cmd_processor_get_mf1_gen1a_magic_mode, NULL }, - { DATA_CMD_SET_MF1_GEN1A_MODE, NULL, cmd_processor_set_mf1_gen1a_magic_mode, NULL }, - { DATA_CMD_GET_MF1_GEN2_MODE, NULL, cmd_processor_get_mf1_gen2_magic_mode, NULL }, - { DATA_CMD_SET_MF1_GEN2_MODE, NULL, cmd_processor_set_mf1_gen2_magic_mode, NULL }, - { DATA_CMD_GET_MF1_USE_FIRST_BLOCK_COLL, NULL, cmd_processor_get_mf1_use_coll_res, NULL }, - { DATA_CMD_SET_MF1_USE_FIRST_BLOCK_COLL, NULL, cmd_processor_set_mf1_use_coll_res, NULL }, - { DATA_CMD_GET_MF1_WRITE_MODE, NULL, cmd_processor_get_mf1_write_mode, NULL }, - { DATA_CMD_SET_MF1_WRITE_MODE, NULL, cmd_processor_set_mf1_write_mode, NULL }, - { DATA_CMD_GET_MF1_ANTI_COLL_DATA, NULL, cmd_processor_get_mf1_anti_coll_data, NULL }, - - { DATA_CMD_SET_SLOT_TAG_NICK, NULL, cmd_processor_set_slot_tag_nick_name, NULL }, - { DATA_CMD_GET_SLOT_TAG_NICK, NULL, cmd_processor_get_slot_tag_nick_name, NULL }, + { DATA_CMD_MF1_WRITE_EMU_BLOCK_DATA, NULL, cmd_processor_mf1_write_emu_block_data, NULL }, + { DATA_CMD_HF14A_SET_ANTI_COLL_DATA, NULL, cmd_processor_hf14a_set_anti_coll_data, NULL }, + + { DATA_CMD_MF1_SET_DETECTION_ENABLE, NULL, cmd_processor_mf1_set_detection_enable, NULL }, + { DATA_CMD_MF1_GET_DETECTION_COUNT, NULL, cmd_processor_mf1_get_detection_count, NULL }, + { DATA_CMD_MF1_GET_DETECTION_LOG, NULL, cmd_processor_mf1_get_detection_log, NULL }, + { DATA_CMD_MF1_GET_DETECTION_ENABLE, NULL, cmd_processor_mf1_get_detection_enable, NULL }, + { DATA_CMD_MF1_READ_EMU_BLOCK_DATA, NULL, cmd_processor_mf1_read_emu_block_data, NULL }, + { DATA_CMD_MF1_GET_EMULATOR_CONFIG, NULL, cmd_processor_mf1_get_emulator_config, NULL }, + { DATA_CMD_MF1_GET_GEN1A_MODE, NULL, cmd_processor_mf1_get_gen1a_mode, NULL }, + { DATA_CMD_MF1_SET_GEN1A_MODE, NULL, cmd_processor_mf1_set_gen1a_mode, NULL }, + { DATA_CMD_MF1_GET_GEN2_MODE, NULL, cmd_processor_mf1_get_gen2_mode, NULL }, + { DATA_CMD_MF1_SET_GEN2_MODE, NULL, cmd_processor_mf1_set_gen2_mode, NULL }, + { DATA_CMD_MF1_GET_BLOCK_ANTI_COLL_MODE, NULL, cmd_processor_mf1_get_block_anti_coll_mode, NULL }, + { DATA_CMD_MF1_SET_BLOCK_ANTI_COLL_MODE, NULL, cmd_processor_mf1_set_block_anti_coll_mode, NULL }, + { DATA_CMD_MF1_GET_WRITE_MODE, NULL, cmd_processor_mf1_get_write_mode, NULL }, + { DATA_CMD_MF1_SET_WRITE_MODE, NULL, cmd_processor_mf1_set_write_mode, NULL }, + { DATA_CMD_HF14A_GET_ANTI_COLL_DATA, NULL, cmd_processor_hf14a_get_anti_coll_data, NULL }, + + { DATA_CMD_EM410X_SET_EMU_ID, NULL, cmd_processor_em410x_set_emu_id, NULL }, + { DATA_CMD_EM410X_GET_EMU_ID, NULL, cmd_processor_em410x_get_emu_id, NULL }, }; - -data_frame_tx_t *cmd_processor_get_capabilities(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - size_t count = sizeof(m_data_cmd_map) / sizeof(m_data_cmd_map[0]); +data_frame_tx_t *cmd_processor_get_device_capabilities(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + size_t count = ARRAYLEN(m_data_cmd_map); uint16_t commands[count]; memset(commands, 0, count * sizeof(uint16_t)); for (size_t i = 0; i < count; i++) { - commands[i] = PP_HTONS(m_data_cmd_map[i].cmd); + commands[i] = U16HTONS(m_data_cmd_map[i].cmd); } - return data_frame_make(cmd, status, count * sizeof(uint16_t), (uint8_t *)commands); -} - - -void cmd_map_init() { - size_t count = sizeof(m_data_cmd_map) / sizeof(m_data_cmd_map[0]); - - for (size_t i = 0; i < count; i++) { - if (m_data_cmd_map[i].cmd == DATA_CMD_GET_DEVICE_CAPABILITIES) { - m_data_cmd_map[i].cmd_processor = cmd_processor_get_capabilities; - return; - } - } + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, count * sizeof(uint16_t), (uint8_t *)commands); } /** @@ -991,7 +1041,7 @@ void cmd_map_init() { * * @param resp data */ -void auto_response_data(data_frame_tx_t *resp) { +static void auto_response_data(data_frame_tx_t *resp) { // TODO Please select the reply source automatically according to the message source, // and do not reply by checking the validity of the link layer by layer if (is_usb_working()) { @@ -1009,9 +1059,6 @@ void auto_response_data(data_frame_tx_t *resp) { void on_data_frame_received(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { data_frame_tx_t *response = NULL; bool is_cmd_support = false; - // print info - NRF_LOG_INFO("Data frame: cmd = %02x, status = %02x, length = %d", cmd, status, length); - NRF_LOG_HEXDUMP_INFO(data, length); for (int i = 0; i < ARRAY_SIZE(m_data_cmd_map); i++) { if (m_data_cmd_map[i].cmd == cmd) { is_cmd_support = true; diff --git a/firmware/application/src/app_cmd.h b/firmware/application/src/app_cmd.h index 02b963e7..538c069d 100644 --- a/firmware/application/src/app_cmd.h +++ b/firmware/application/src/app_cmd.h @@ -15,6 +15,5 @@ typedef struct { } cmd_data_map_t; void on_data_frame_received(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data); -void cmd_map_init(); #endif diff --git a/firmware/application/src/app_config.h b/firmware/application/src/app_config.h index a885958f..5dc7023b 100644 --- a/firmware/application/src/app_config.h +++ b/firmware/application/src/app_config.h @@ -2,8 +2,11 @@ // version code, 1byte, max = 0 -> 255 -#define APP_FW_VER_MAJOR 1 -#define APP_FW_VER_MINOR 0 +//#define APP_FW_VER_MAJOR 2 +//#define APP_FW_VER_MINOR 0 +#if !(defined APP_FW_VER_MAJOR && defined APP_FW_VER_MINOR) + #error You need to define APP_FW_VER_MAJOR and APP_FW_VER_MINOR +#endif // Merge major and minor version code to U16 value. #define FW_VER_NUM VER_CODE_TO_NUM(APP_FW_VER_MAJOR, APP_FW_VER_MINOR) diff --git a/firmware/application/src/app_main.c b/firmware/application/src/app_main.c index 030cd387..a6eeab14 100644 --- a/firmware/application/src/app_main.c +++ b/firmware/application/src/app_main.c @@ -250,7 +250,7 @@ static void system_off_enter(void) { if (g_is_low_battery_shutdown) { // Don't create too complex animations, just blink LED1 three times. rgb_marquee_stop(); - set_slot_light_color(0); + set_slot_light_color(RGB_RED); for (uint8_t i = 0; i <= 3; i++) { nrf_gpio_pin_set(LED_1); bsp_delay_ms(100); @@ -570,8 +570,8 @@ static void btn_fn_copy_ic_uid(void) { uint8_t id_buffer[5] = { 0x00 }; // get 14a tag res buffer; uint8_t slot_now = tag_emulation_get_slot(); - tag_specific_type_t tag_type[2]; - tag_emulation_get_specific_type_by_slot(slot_now, tag_type); + tag_slot_specific_type_t tag_types; + tag_emulation_get_specific_types_by_slot(slot_now, &tag_types); nfc_tag_14a_coll_res_entity_t *antres; @@ -584,7 +584,7 @@ static void btn_fn_copy_ic_uid(void) { NRF_LOG_INFO("Start reader mode to offline copy.") } - switch (tag_type[1]) { + switch (tag_types.tag_lf) { case TAG_TYPE_EM410X: status = PcdScanEM410X(id_buffer); @@ -599,7 +599,7 @@ static void btn_fn_copy_ic_uid(void) { offline_status_error(); } break; - case TAG_TYPE_UNKNOWN: + case TAG_TYPE_UNDEFINED: // empty LF slot, nothing to do, move on to HF break; default: @@ -607,8 +607,8 @@ static void btn_fn_copy_ic_uid(void) { offline_status_error(); } - tag_data_buffer_t *buffer = get_buffer_by_tag_type(tag_type[0]); - switch (tag_type[0]) { + tag_data_buffer_t *buffer = get_buffer_by_tag_type(tag_types.tag_hf); + switch (tag_types.tag_hf) { case TAG_TYPE_MIFARE_Mini: case TAG_TYPE_MIFARE_1024: case TAG_TYPE_MIFARE_2048: @@ -626,7 +626,7 @@ static void btn_fn_copy_ic_uid(void) { break; } - case TAG_TYPE_UNKNOWN: + case TAG_TYPE_UNDEFINED: // empty HF slot, nothing to do goto exit; @@ -648,11 +648,15 @@ static void btn_fn_copy_ic_uid(void) { status = pcd_14a_reader_scan_auto(&tag); if (status == HF_TAG_OK) { // copy uid + antres->size = tag.uid_len; memcpy(antres->uid, tag.uid, tag.uid_len); // copy atqa memcpy(antres->atqa, tag.atqa, 2); // copy sak antres->sak[0] = tag.sak; + // copy ats + antres->ats.length = tag.ats_len; + memcpy(antres->ats.data, tag.ats, tag.ats_len); NRF_LOG_INFO("Offline HF uid copied") offline_status_ok(); } else { @@ -771,7 +775,6 @@ static void ble_passkey_init(void) { */ int main(void) { hw_connect_init(); // Remember to initialize the pins first - cmd_map_init(); // Set function in CMD map for DATA_CMD_GET_DEVICE_CAPABILITIES fds_util_init(); // Initialize fds tool settings_load_config(); // Load settings from flash diff --git a/firmware/application/src/app_status.h b/firmware/application/src/app_status.h index 44ddc010..574768df 100644 --- a/firmware/application/src/app_status.h +++ b/firmware/application/src/app_status.h @@ -13,18 +13,7 @@ #define HF_ERR_BCC (0x05) // IC card BCC error #define MF_ERR_AUTH (0x06) // MF card verification failed #define HF_ERR_PARITY (0x07) // IC card parity error - - -///////////////////////////////////////////////////////////////////// -// MIFARE status -///////////////////////////////////////////////////////////////////// -#define DARKSIDE_CANT_FIXED_NT (0x20) // Darkside, the random number cannot be fixed, this situation may appear on the UID card -#define DARKSIDE_LUCK_AUTH_OK (0x21) // Darkside, the direct verification is successful, maybe the key is just empty -#define DARKSIDE_NACK_NO_SEND (0x22) // Darkside, the card does not respond to NACK, it may be a card that fixes Nack logic vulnerabilities -#define DARKSIDE_TAG_CHANGED (0x23) // Darkside, card switching in the process of running DARKSIDE, May is the two cards quickly switched -#define NESTED_TAG_IS_STATIC (0x24) // Nested, the random number of the card response is fixed -#define NESTED_TAG_IS_HARD (0x25) // Nested, the random number of the card response is unpredictable - +#define HF_ERR_ATS (0x08) // ATS should be present but card NAKed ///////////////////////////////////////////////////////////////////// // lf status diff --git a/firmware/application/src/ble_main.c b/firmware/application/src/ble_main.c index 464caaa5..79cfb7ef 100644 --- a/firmware/application/src/ble_main.c +++ b/firmware/application/src/ble_main.c @@ -106,8 +106,8 @@ static ble_opt_t m_static_pin_option; * @details This function will set up the ble connect passkey. */ void set_ble_connect_key(uint8_t *key) { - static uint8_t passkey[BLE_CONNECT_KEY_LEN_MAX]; - memcpy(passkey, key, BLE_CONNECT_KEY_LEN_MAX); + static uint8_t passkey[BLE_PAIRING_KEY_LEN]; + memcpy(passkey, key, BLE_PAIRING_KEY_LEN); m_static_pin_option.gap_opt.passkey.p_passkey = passkey; // NRF_LOG_RAW_HEXDUMP_INFO(passkey, 6); APP_ERROR_CHECK(sd_ble_opt_set(BLE_GAP_OPT_PASSKEY, &m_static_pin_option)); @@ -331,7 +331,7 @@ static void services_init(void) { bas_init_obj.bl_cccd_wr_sec = SEC_OPEN; bas_init_obj.bl_report_rd_sec = SEC_OPEN; } - + err_code = ble_bas_init(&m_bas, &bas_init_obj); APP_ERROR_CHECK(err_code); } diff --git a/firmware/application/src/data_cmd.h b/firmware/application/src/data_cmd.h index f8595012..267e09c4 100644 --- a/firmware/application/src/data_cmd.h +++ b/firmware/application/src/data_cmd.h @@ -10,7 +10,7 @@ #define DATA_CMD_GET_APP_VERSION (1000) #define DATA_CMD_CHANGE_DEVICE_MODE (1001) #define DATA_CMD_GET_DEVICE_MODE (1002) -#define DATA_CMD_SET_SLOT_ACTIVATED (1003) +#define DATA_CMD_SET_ACTIVE_SLOT (1003) #define DATA_CMD_SET_SLOT_TAG_TYPE (1004) #define DATA_CMD_SET_SLOT_DATA_DEFAULT (1005) #define DATA_CMD_SET_SLOT_ENABLE (1006) @@ -28,6 +28,7 @@ #define DATA_CMD_GET_ACTIVE_SLOT (1018) #define DATA_CMD_GET_SLOT_INFO (1019) #define DATA_CMD_WIPE_FDS (1020) + #define DATA_CMD_GET_ENABLED_SLOTS (1023) #define DATA_CMD_DELETE_SLOT_SENSE_TYPE (1024) #define DATA_CMD_GET_BATTERY_INFO (1025) @@ -35,11 +36,11 @@ #define DATA_CMD_SET_BUTTON_PRESS_CONFIG (1027) #define DATA_CMD_GET_LONG_BUTTON_PRESS_CONFIG (1028) #define DATA_CMD_SET_LONG_BUTTON_PRESS_CONFIG (1029) -#define DATA_CMD_SET_BLE_CONNECT_KEY_CONFIG (1030) -#define DATA_CMD_GET_BLE_CONNECT_KEY_CONFIG (1031) +#define DATA_CMD_SET_BLE_PAIRING_KEY (1030) +#define DATA_CMD_GET_BLE_PAIRING_KEY (1031) #define DATA_CMD_DELETE_ALL_BLE_BONDS (1032) -#define DATA_CMD_GET_DEVICE (1033) -#define DATA_CMD_GET_SETTINGS (1034) +#define DATA_CMD_GET_DEVICE_MODEL (1033) +#define DATA_CMD_GET_DEVICE_SETTINGS (1034) #define DATA_CMD_GET_DEVICE_CAPABILITIES (1035) #define DATA_CMD_GET_BLE_PAIRING_ENABLE (1036) #define DATA_CMD_SET_BLE_PAIRING_ENABLE (1037) @@ -53,16 +54,18 @@ // Range from 2000 -> 2999 // ****************************************************************** // -#define DATA_CMD_SCAN_14A_TAG (2000) -#define DATA_CMD_MF1_SUPPORT_DETECT (2001) -#define DATA_CMD_MF1_NT_LEVEL_DETECT (2002) -#define DATA_CMD_MF1_DARKSIDE_DETECT (2003) +#define DATA_CMD_HF14A_SCAN (2000) +#define DATA_CMD_MF1_DETECT_SUPPORT (2001) +#define DATA_CMD_MF1_DETECT_PRNG (2002) +#define DATA_CMD_MF1_STATIC_NESTED_ACQUIRE (2003) #define DATA_CMD_MF1_DARKSIDE_ACQUIRE (2004) -#define DATA_CMD_MF1_NT_DIST_DETECT (2005) +#define DATA_CMD_MF1_DETECT_NT_DIST (2005) #define DATA_CMD_MF1_NESTED_ACQUIRE (2006) -#define DATA_CMD_MF1_CHECK_ONE_KEY_BLOCK (2007) +#define DATA_CMD_MF1_AUTH_ONE_KEY_BLOCK (2007) #define DATA_CMD_MF1_READ_ONE_BLOCK (2008) #define DATA_CMD_MF1_WRITE_ONE_BLOCK (2009) +#define DATA_CMD_HF14A_RAW (2010) + // // ****************************************************************** @@ -72,8 +75,8 @@ // Range from 3000 -> 3999 // ****************************************************************** // -#define DATA_CMD_SCAN_EM410X_TAG (3000) -#define DATA_CMD_WRITE_EM410X_TO_T5577 (3001) +#define DATA_CMD_EM410X_SCAN (3000) +#define DATA_CMD_EM410X_WRITE_TO_T55XX (3001) // // ****************************************************************** @@ -83,25 +86,23 @@ // Range from 4000 -> 4999 // ****************************************************************** // -#define DATA_CMD_LOAD_MF1_EMU_BLOCK_DATA (4000) -#define DATA_CMD_SET_MF1_ANTI_COLLISION_RES (4001) -#define DATA_CMD_SET_MF1_ANTI_COLLISION_INFO (4002) -#define DATA_CMD_SET_MF1_ATS_RESOURCE (4003) -#define DATA_CMD_SET_MF1_DETECTION_ENABLE (4004) -#define DATA_CMD_GET_MF1_DETECTION_COUNT (4005) -#define DATA_CMD_GET_MF1_DETECTION_RESULT (4006) -#define DATA_CMD_GET_MF1_DETECTION_STATUS (4007) -#define DATA_CMD_READ_MF1_EMU_BLOCK_DATA (4008) -#define DATA_CMD_GET_MF1_EMULATOR_CONFIG (4009) -#define DATA_CMD_GET_MF1_GEN1A_MODE (4010) -#define DATA_CMD_SET_MF1_GEN1A_MODE (4011) -#define DATA_CMD_GET_MF1_GEN2_MODE (4012) -#define DATA_CMD_SET_MF1_GEN2_MODE (4013) -#define DATA_CMD_GET_MF1_USE_FIRST_BLOCK_COLL (4014) -#define DATA_CMD_SET_MF1_USE_FIRST_BLOCK_COLL (4015) -#define DATA_CMD_GET_MF1_WRITE_MODE (4016) -#define DATA_CMD_SET_MF1_WRITE_MODE (4017) -#define DATA_CMD_GET_MF1_ANTI_COLL_DATA (4018) +#define DATA_CMD_MF1_WRITE_EMU_BLOCK_DATA (4000) +#define DATA_CMD_HF14A_SET_ANTI_COLL_DATA (4001) +#define DATA_CMD_MF1_SET_DETECTION_ENABLE (4004) +#define DATA_CMD_MF1_GET_DETECTION_COUNT (4005) +#define DATA_CMD_MF1_GET_DETECTION_LOG (4006) +#define DATA_CMD_MF1_GET_DETECTION_ENABLE (4007) +#define DATA_CMD_MF1_READ_EMU_BLOCK_DATA (4008) +#define DATA_CMD_MF1_GET_EMULATOR_CONFIG (4009) +#define DATA_CMD_MF1_GET_GEN1A_MODE (4010) +#define DATA_CMD_MF1_SET_GEN1A_MODE (4011) +#define DATA_CMD_MF1_GET_GEN2_MODE (4012) +#define DATA_CMD_MF1_SET_GEN2_MODE (4013) +#define DATA_CMD_MF1_GET_BLOCK_ANTI_COLL_MODE (4014) +#define DATA_CMD_MF1_SET_BLOCK_ANTI_COLL_MODE (4015) +#define DATA_CMD_MF1_GET_WRITE_MODE (4016) +#define DATA_CMD_MF1_SET_WRITE_MODE (4017) +#define DATA_CMD_HF14A_GET_ANTI_COLL_DATA (4018) // // ****************************************************************** @@ -114,7 +115,7 @@ // // ****************************************************************** -#define DATA_CMD_SET_EM410X_EMU_ID (5000) -#define DATA_CMD_GET_EM410X_EMU_ID (5001) +#define DATA_CMD_EM410X_SET_EMU_ID (5000) +#define DATA_CMD_EM410X_GET_EMU_ID (5001) #endif diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_14a.c b/firmware/application/src/rfid/nfctag/hf/nfc_14a.c index 40cc6439..6e01c9aa 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_14a.c +++ b/firmware/application/src/rfid/nfctag/hf/nfc_14a.c @@ -617,7 +617,7 @@ void nfc_tag_14a_event_callback(nrfx_nfct_evt_t const *p_event) { g_is_tag_emulating = true; g_usb_led_marquee_enable = false; - set_slot_light_color(1); + set_slot_light_color(RGB_GREEN); TAG_FIELD_LED_ON() NRF_LOG_INFO("HF FIELD DETECTED"); @@ -661,7 +661,7 @@ void nfc_tag_14a_event_callback(nrfx_nfct_evt_t const *p_event) { break; } case NRFX_NFCT_EVT_RX_FRAMEEND: { - set_slot_light_color(1); + set_slot_light_color(RGB_GREEN); TAG_FIELD_LED_ON() // NRF_LOG_INFO("RX FRAMEEND.\n"); diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf1.c b/firmware/application/src/rfid/nfctag/hf/nfc_mf1.c index 129e7b9d..417c6753 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf1.c +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf1.c @@ -344,10 +344,11 @@ void append_mf1_auth_log_step1(bool isKeyB, bool isNested, uint8_t block, uint8_ } // Determine whether this card slot enables the detection log record if (m_tag_information->config.detection_enable) { - m_auth_log.logs[m_auth_log.count].cmd.is_key_b = isKeyB; - m_auth_log.logs[m_auth_log.count].cmd.block = block; - m_auth_log.logs[m_auth_log.count].cmd.is_nested = isNested; + m_auth_log.logs[m_auth_log.count].is_key_b = isKeyB; + m_auth_log.logs[m_auth_log.count].block = block; + m_auth_log.logs[m_auth_log.count].is_nested = isNested; memcpy(m_auth_log.logs[m_auth_log.count].uid, UID_BY_CASCADE_LEVEL, 4); +// m_auth_log.logs[m_auth_log.count].nt = U32HTONL(*(uint32_t *)nonce); memcpy(m_auth_log.logs[m_auth_log.count].nt, nonce, 4); } } @@ -363,6 +364,8 @@ void append_mf1_auth_log_step2(uint8_t *nr, uint8_t *ar) { } if (m_tag_information->config.detection_enable) { // Cache encryption information +// m_auth_log.logs[m_auth_log.count].nr = U32HTONL(*(uint32_t *)nr); +// m_auth_log.logs[m_auth_log.count].ar = U32HTONL(*(uint32_t *)ar); memcpy(m_auth_log.logs[m_auth_log.count].nr, nr, 4); memcpy(m_auth_log.logs[m_auth_log.count].ar, ar, 4); } @@ -388,7 +391,7 @@ void append_mf1_auth_log_step3(bool is_auth_success) { /** @brief MF1 obtain verification log * @param count: The statistics of the verification log */ -nfc_tag_mf1_auth_log_t *get_mf1_auth_log(uint32_t *count) { +nfc_tag_mf1_auth_log_t *mf1_get_auth_log(uint32_t *count) { // First pass the total number of logs verified by verified *count = m_auth_log.count; // Just return to the head pointer of the log number array @@ -1111,11 +1114,15 @@ static int get_information_size_by_tag_type(tag_specific_type_t type, bool auth_ * @return The length of the data that needs to be saved is that it does not save when 0 */ int nfc_tag_mf1_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buffer) { - if (m_tag_type != TAG_TYPE_UNKNOWN) { + if (m_tag_type != TAG_TYPE_UNDEFINED) { if (m_tag_information->config.mode_block_write == NFC_TAG_MF1_WRITE_SHADOW) { NRF_LOG_INFO("The mf1 is shadow write mode."); return 0; } + if (m_tag_information->config.mode_block_write == NFC_TAG_MF1_WRITE_SHADOW_REQ) { + NRF_LOG_INFO("The mf1 will be set to shadow write mode."); + m_tag_information->config.mode_block_write = NFC_TAG_MF1_WRITE_SHADOW; + } // Save the corresponding size data according to the current label type return get_information_size_by_tag_type(type, false); } else { @@ -1256,6 +1263,9 @@ bool nfc_tag_mf1_is_use_mf1_coll_res(void) { // Set write mode void nfc_tag_mf1_set_write_mode(nfc_tag_mf1_write_mode_t write_mode) { + if (write_mode == NFC_TAG_MF1_WRITE_SHADOW) { + write_mode = NFC_TAG_MF1_WRITE_SHADOW_REQ; + } m_tag_information->config.mode_block_write = write_mode; } diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf1.h b/firmware/application/src/rfid/nfctag/hf/nfc_mf1.h index f07d0e46..35217149 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf1.h +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf1.h @@ -2,6 +2,7 @@ #define NFC_MF1_H #include "nfc_14a.h" +#include "netdata.h" // Exchange space for time. // Fast simulate enable(Implement By ChameleonMini Repo) @@ -14,10 +15,11 @@ //MF1 label writing mode typedef enum { - NFC_TAG_MF1_WRITE_NORMAL = 0u, - NFC_TAG_MF1_WRITE_DENIED = 1u, - NFC_TAG_MF1_WRITE_DECEIVE = 2u, - NFC_TAG_MF1_WRITE_SHADOW = 3u, + NFC_TAG_MF1_WRITE_NORMAL = 0u, + NFC_TAG_MF1_WRITE_DENIED = 1u, + NFC_TAG_MF1_WRITE_DECEIVE = 2u, + NFC_TAG_MF1_WRITE_SHADOW = 3u, + NFC_TAG_MF1_WRITE_SHADOW_REQ = 4u, } nfc_tag_mf1_write_mode_t; // MF1 tag Gen1a mode state machine @@ -121,22 +123,23 @@ typedef struct { // MF1 label verification history typedef struct { // Basic information of verification - struct { - uint8_t block; - uint8_t is_key_b: 1; - uint8_t is_nested: 1; - // Airspace, occupying positions - uint8_t : 6; - } cmd; + uint8_t block; + uint8_t is_key_b: 1; + uint8_t is_nested: 1; + // padding to full byte + uint8_t : 6; // MFKEY32 necessary parameters uint8_t uid[4]; uint8_t nt[4]; uint8_t nr[4]; uint8_t ar[4]; -} nfc_tag_mf1_auth_log_t; + // uint32_t nt; + // uint32_t nr; + // uint32_t ar; +} PACKED nfc_tag_mf1_auth_log_t; -nfc_tag_mf1_auth_log_t *get_mf1_auth_log(uint32_t *count); +nfc_tag_mf1_auth_log_t *mf1_get_auth_log(uint32_t *count); int nfc_tag_mf1_data_loadcb(tag_specific_type_t type, tag_data_buffer_t *buffer); int nfc_tag_mf1_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buffer); bool nfc_tag_mf1_data_factory(uint8_t slot, tag_specific_type_t tag_type); diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_ntag.c b/firmware/application/src/rfid/nfctag/hf/nfc_ntag.c index b95362c3..b5627d5d 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_ntag.c +++ b/firmware/application/src/rfid/nfctag/hf/nfc_ntag.c @@ -214,7 +214,7 @@ static int get_information_size_by_tag_type(tag_specific_type_t type) { * @return to be saved, the length of the data that needs to be saved, it means not saved when 0 */ int nfc_tag_ntag_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buffer) { - if (m_tag_type != TAG_TYPE_UNKNOWN) { + if (m_tag_type != TAG_TYPE_UNDEFINED) { // Save the corresponding size data according to the current label type return get_information_size_by_tag_type(type); } else { diff --git a/firmware/application/src/rfid/nfctag/lf/lf_tag_em.c b/firmware/application/src/rfid/nfctag/lf/lf_tag_em.c index c32341ff..9d480557 100644 --- a/firmware/application/src/rfid/nfctag/lf/lf_tag_em.c +++ b/firmware/application/src/rfid/nfctag/lf/lf_tag_em.c @@ -41,7 +41,7 @@ static volatile bool m_is_lf_emulating = false; // The timer of the delivery card number, we use the timer 3 const nrfx_timer_t m_timer_send_id = NRFX_TIMER_INSTANCE(3); // Cache label type -static tag_specific_type_t m_tag_type = TAG_TYPE_UNKNOWN; +static tag_specific_type_t m_tag_type = TAG_TYPE_UNDEFINED; /** * @brief Convert the card number of EM410X to the memory layout of U64 and calculate the puppet school inspection @@ -299,7 +299,7 @@ static void lpcomp_event_handler(nrf_lpcomp_event_t event) { g_usb_led_marquee_enable = false; // LED status update - set_slot_light_color(2); + set_slot_light_color(RGB_BLUE); TAG_FIELD_LED_ON() //In any case, every time the state finds changes, you need to reset the BIT location of the sending @@ -396,7 +396,7 @@ int lf_tag_em410x_data_loadcb(tag_specific_type_t type, tag_data_buffer_t *buffe */ int lf_tag_em410x_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buffer) { // Make sure to load this label before allowing saving - if (m_tag_type != TAG_TYPE_UNKNOWN) { + if (m_tag_type != TAG_TYPE_UNDEFINED) { // Just save the original card package directly return LF_EM410X_TAG_ID_SIZE; } else { diff --git a/firmware/application/src/rfid/nfctag/tag_base_type.h b/firmware/application/src/rfid/nfctag/tag_base_type.h index 0e874a18..9f6b2027 100644 --- a/firmware/application/src/rfid/nfctag/tag_base_type.h +++ b/firmware/application/src/rfid/nfctag/tag_base_type.h @@ -18,21 +18,100 @@ typedef enum { * Note that all the defined label type below is the specific type statistics of the application layer refine * No longer distinguish between high and low frequencies */ + typedef enum { - //Specific and necessary signs do not exist - TAG_TYPE_UNKNOWN, - //125kHz (ID card) series - TAG_TYPE_EM410X, - // MiFare series - TAG_TYPE_MIFARE_Mini, + TAG_TYPE_UNDEFINED = 0, + + // old HL/LF common types, slots using these ones need to be migrated first + OLD_TAG_TYPE_EM410X, + OLD_TAG_TYPE_MIFARE_Mini, + OLD_TAG_TYPE_MIFARE_1024, + OLD_TAG_TYPE_MIFARE_2048, + OLD_TAG_TYPE_MIFARE_4096, + OLD_TAG_TYPE_NTAG_213, + OLD_TAG_TYPE_NTAG_215, + OLD_TAG_TYPE_NTAG_216, + + //////////// LF //////////// + + //////// ASK Tag-Talk-First 100 + // EM410x + TAG_TYPE_EM410X = 100, + // FDX-B + // securakey + // gallagher + // PAC/Stanley + // Presco + // Visa2000 + // Viking + // Noralsy + // Jablotron + + //////// FSK Tag-Talk-First 200 + // HID Prox + // ioProx + // AWID + // Paradox + + //////// PSK Tag-Talk-First 300 + // Indala + // Keri + // NexWatch + + //////// Reader-Talk-First 400 + // T5577 + // EM4x05/4x69 + // EM4x50/4x70 + // Hitag series + + //////////// HF //////////// + + // MIFARE Classic series 1000 + TAG_TYPE_MIFARE_Mini = 1000, TAG_TYPE_MIFARE_1024, TAG_TYPE_MIFARE_2048, TAG_TYPE_MIFARE_4096, - // NTAG series - TAG_TYPE_NTAG_213, + // MFUL / NTAG series 1100 + TAG_TYPE_NTAG_213 = 1100, TAG_TYPE_NTAG_215, TAG_TYPE_NTAG_216, + // MIFARE Plus series 1200 + // DESFire series 1300 + + // ST25TA series 2000 + + // HF14A-4 series 3000 + } tag_specific_type_t; +#define TAG_SPECIFIC_TYPE_OLD2NEW_LF_VALUES \ + {OLD_TAG_TYPE_EM410X, TAG_TYPE_EM410X} + +#define TAG_SPECIFIC_TYPE_OLD2NEW_HF_VALUES \ + {OLD_TAG_TYPE_MIFARE_Mini, TAG_TYPE_MIFARE_Mini},\ + {OLD_TAG_TYPE_MIFARE_1024, TAG_TYPE_MIFARE_1024},\ + {OLD_TAG_TYPE_MIFARE_2048, TAG_TYPE_MIFARE_2048},\ + {OLD_TAG_TYPE_MIFARE_4096, TAG_TYPE_MIFARE_4096},\ + {OLD_TAG_TYPE_NTAG_213, TAG_TYPE_NTAG_213},\ + {OLD_TAG_TYPE_NTAG_215, TAG_TYPE_NTAG_215},\ + {OLD_TAG_TYPE_NTAG_216, TAG_TYPE_NTAG_216} + +#define TAG_SPECIFIC_TYPE_LF_VALUES \ + TAG_TYPE_EM410X + +#define TAG_SPECIFIC_TYPE_HF_VALUES \ + TAG_TYPE_MIFARE_Mini,\ + TAG_TYPE_MIFARE_1024,\ + TAG_TYPE_MIFARE_2048,\ + TAG_TYPE_MIFARE_4096,\ + TAG_TYPE_NTAG_213,\ + TAG_TYPE_NTAG_215,\ + TAG_TYPE_NTAG_216 + +typedef struct { + tag_specific_type_t tag_hf; + tag_specific_type_t tag_lf; +} tag_slot_specific_type_t; + #endif diff --git a/firmware/application/src/rfid/nfctag/tag_emulation.c b/firmware/application/src/rfid/nfctag/tag_emulation.c index a3d7079d..a7ebdabb 100644 --- a/firmware/application/src/rfid/nfctag/tag_emulation.c +++ b/firmware/application/src/rfid/nfctag/tag_emulation.c @@ -32,7 +32,21 @@ NRF_LOG_MODULE_REGISTER(); // Is the logo in the analog card? bool g_is_tag_emulating = false; +static tag_specific_type_t tag_specific_type_old2new_lf_values[][2] = { TAG_SPECIFIC_TYPE_OLD2NEW_LF_VALUES }; +static tag_specific_type_t tag_specific_type_old2new_hf_values[][2] = { TAG_SPECIFIC_TYPE_OLD2NEW_HF_VALUES }; +static tag_specific_type_t tag_specific_type_lf_values[] = { TAG_SPECIFIC_TYPE_LF_VALUES }; +static tag_specific_type_t tag_specific_type_hf_values[] = { TAG_SPECIFIC_TYPE_HF_VALUES }; +bool is_tag_specific_type_valid(tag_specific_type_t tag_type) { + bool valid = false; + for (uint16_t i = 0; i < ARRAYLEN(tag_specific_type_lf_values); i++) { + valid |= (tag_type == tag_specific_type_lf_values[i]); + } + for (uint16_t i = 0; i < ARRAYLEN(tag_specific_type_hf_values); i++) { + valid |= (tag_type == tag_specific_type_hf_values[i]); + } + return valid; +} // ********************** Specific parameters start ********************** /** @@ -51,17 +65,19 @@ static tag_data_buffer_t m_tag_data_hf = { sizeof(m_tag_data_buffer_hf), m_tag_d */ static tag_slot_config_t slotConfig ALIGN_U32 = { // Configure activated card slot, default activation of the 0th card slot (the first card) - .config = { .activated = 0, .reserved1 = 0, .reserved2 = 0, .reserved3 = 0, }, - // Configuration card slot group - .group = { - { .enable = true, .reserved1 = 0, .reserved2 = 0, .tag_hf = TAG_TYPE_MIFARE_1024, .tag_lf = TAG_TYPE_EM410X, }, // 1 - { .enable = true, .reserved1 = 0, .reserved2 = 0, .tag_hf = TAG_TYPE_MIFARE_1024, .tag_lf = TAG_TYPE_UNKNOWN, }, // 2 - { .enable = true, .reserved1 = 0, .reserved2 = 0, .tag_hf = TAG_TYPE_UNKNOWN, .tag_lf = TAG_TYPE_EM410X, }, // 3 - { .enable = false, .reserved1 = 0, .reserved2 = 0, .tag_hf = TAG_TYPE_UNKNOWN, .tag_lf = TAG_TYPE_UNKNOWN, }, // 4 - { .enable = false, .reserved1 = 0, .reserved2 = 0, .tag_hf = TAG_TYPE_UNKNOWN, .tag_lf = TAG_TYPE_UNKNOWN, }, // 5 - { .enable = false, .reserved1 = 0, .reserved2 = 0, .tag_hf = TAG_TYPE_UNKNOWN, .tag_lf = TAG_TYPE_UNKNOWN, }, // 6 - { .enable = false, .reserved1 = 0, .reserved2 = 0, .tag_hf = TAG_TYPE_UNKNOWN, .tag_lf = TAG_TYPE_UNKNOWN, }, // 7 - { .enable = false, .reserved1 = 0, .reserved2 = 0, .tag_hf = TAG_TYPE_UNKNOWN, .tag_lf = TAG_TYPE_UNKNOWN, }, // 8 + .version = TAG_SLOT_CONFIG_CURRENT_VERSION, + .active_slot = 0, + // Configuration card slots + // See tag_emulation_factory_init for actual tag content + .slots = { + { .enabled_hf = true, .enabled_lf = true, .tag_hf = TAG_TYPE_MIFARE_1024, .tag_lf = TAG_TYPE_EM410X, }, // 1 + { .enabled_hf = true, .enabled_lf = false, .tag_hf = TAG_TYPE_MIFARE_1024, .tag_lf = TAG_TYPE_UNDEFINED, }, // 2 + { .enabled_hf = true, .enabled_lf = true, .tag_hf = TAG_TYPE_UNDEFINED, .tag_lf = TAG_TYPE_EM410X, }, // 3 + { .enabled_hf = false, .enabled_lf = false, .tag_hf = TAG_TYPE_UNDEFINED, .tag_lf = TAG_TYPE_UNDEFINED, }, // 4 + { .enabled_hf = false, .enabled_lf = false, .tag_hf = TAG_TYPE_UNDEFINED, .tag_lf = TAG_TYPE_UNDEFINED, }, // 5 + { .enabled_hf = false, .enabled_lf = false, .tag_hf = TAG_TYPE_UNDEFINED, .tag_lf = TAG_TYPE_UNDEFINED, }, // 6 + { .enabled_hf = false, .enabled_lf = false, .tag_hf = TAG_TYPE_UNDEFINED, .tag_lf = TAG_TYPE_UNDEFINED, }, // 7 + { .enabled_hf = false, .enabled_lf = false, .tag_hf = TAG_TYPE_UNDEFINED, .tag_lf = TAG_TYPE_UNDEFINED, }, // 8 }, }; // The card slot configuration unique CRC, once the slot configuration changes, can be checked by CRC @@ -90,6 +106,9 @@ static tag_base_handler_map_t tag_base_map[] = { }; +static void tag_emulation_load_config(void); +static void tag_emulation_save_config(void); + /** * accordingToTheSpecifiedDetailedLabelType,ObtainTheImplementationFunctionOfTheDataThatProcessesTheLoadedLoaded */ @@ -176,7 +195,7 @@ bool tag_emulation_load_by_buffer(tag_specific_type_t tag_type, bool update_crc) */ static void load_data_by_tag_type(uint8_t slot, tag_specific_type_t tag_type) { // maybeTheCardSlotIsNotEnabledToUseTheSimulationOfThisTypeOfLabel,AndSkipTheDataDirectlyToLoadThisData - if (tag_type == TAG_TYPE_UNKNOWN) { + if (tag_type == TAG_TYPE_UNDEFINED) { return; } // getTheSpecialBufferInformation @@ -190,7 +209,8 @@ static void load_data_by_tag_type(uint8_t slot, tag_specific_type_t tag_type) { fds_slot_record_map_t map_info; get_fds_map_by_slot_sense_type_for_dump(slot, sense_type, &map_info); // accordingToTheTypeOfTheCardSlotCurrentlyActivated,LoadTheDataOfTheDesignatedFieldToTheBuffer //Tip:IfTheLengthOfTheDataCannotMatchTheLengthOfTheBuffer,ItMayBeCausedByTheFirmwareUpdateAtThisTime,TheDataMustBeDeletedAndRebuilt - bool ret = fds_read_sync(map_info.id, map_info.key, buffer->length, buffer->buffer); + uint16_t length = buffer->length; + bool ret = fds_read_sync(map_info.id, map_info.key, &length, buffer->buffer); if (false == ret) { NRF_LOG_INFO("Tag slot data no exists."); return; @@ -206,7 +226,7 @@ static void load_data_by_tag_type(uint8_t slot, tag_specific_type_t tag_type) { */ static void save_data_by_tag_type(uint8_t slot, tag_specific_type_t tag_type) { // Maybe the card slot is not enabled to use the simulation of this type of label, and skip it directly to save this data - if (tag_type == TAG_TYPE_UNKNOWN) { + if (tag_type == TAG_TYPE_UNDEFINED) { return; } tag_data_buffer_t *buffer = get_buffer_by_tag_type(tag_type); @@ -276,8 +296,8 @@ static void delete_data_by_tag_type(uint8_t slot, tag_sense_type_t sense_type) { */ void tag_emulation_load_data(void) { uint8_t slot = tag_emulation_get_slot(); - load_data_by_tag_type(slot, slotConfig.group[slot].tag_hf); - load_data_by_tag_type(slot, slotConfig.group[slot].tag_lf); + load_data_by_tag_type(slot, slotConfig.slots[slot].tag_hf); + load_data_by_tag_type(slot, slotConfig.slots[slot].tag_lf); } /** @@ -285,8 +305,8 @@ void tag_emulation_load_data(void) { */ void tag_emulation_save_data(void) { uint8_t slot = tag_emulation_get_slot(); - save_data_by_tag_type(slot, slotConfig.group[slot].tag_hf); - save_data_by_tag_type(slot, slotConfig.group[slot].tag_lf); + save_data_by_tag_type(slot, slotConfig.slots[slot].tag_hf); + save_data_by_tag_type(slot, slotConfig.slots[slot].tag_lf); } /** @@ -295,9 +315,9 @@ void tag_emulation_save_data(void) { * @param slot Card slot * @param tag_type Label */ -void tag_emulation_get_specific_type_by_slot(uint8_t slot, tag_specific_type_t tag_type[2]) { - tag_type[0] = slotConfig.group[slot].tag_hf; - tag_type[1] = slotConfig.group[slot].tag_lf; +void tag_emulation_get_specific_types_by_slot(uint8_t slot, tag_slot_specific_type_t *tag_types) { + tag_types->tag_hf = slotConfig.slots[slot].tag_hf; + tag_types->tag_lf = slotConfig.slots[slot].tag_lf; } /** @@ -309,24 +329,22 @@ void tag_emulation_delete_data(uint8_t slot, tag_sense_type_t sense_type) { //Close the corresponding card type of the corresponding card slot switch (sense_type) { case TAG_SENSE_HF: { - slotConfig.group[slot].tag_hf = TAG_TYPE_UNKNOWN; + slotConfig.slots[slot].tag_hf = TAG_TYPE_UNDEFINED; + slotConfig.slots[slot].enabled_hf = false; } break; case TAG_SENSE_LF: { - slotConfig.group[slot].tag_lf = TAG_TYPE_UNKNOWN; + slotConfig.slots[slot].tag_lf = TAG_TYPE_UNDEFINED; + slotConfig.slots[slot].enabled_lf = false; } break; default: break; } // If the deleted card slot data is currently activated (being simulated), we also need to make dynamic shutdown - if (slotConfig.config.activated == slot) { + if (slotConfig.active_slot == slot) { tag_emulation_sense_switch(sense_type, false); } - // If we find that the two cards of this card groove are gone, we have to close this card slot. - if (slotConfig.group[slot].tag_hf == TAG_TYPE_UNKNOWN && slotConfig.group[slot].tag_lf == TAG_TYPE_UNKNOWN) { - slotConfig.group[slot].enable = false; - } } /** @@ -353,14 +371,14 @@ bool tag_emulation_factory_data(uint8_t slot, tag_specific_type_t tag_type) { */ static void tag_emulation_sense_switch_all(bool enable) { uint8_t slot = tag_emulation_get_slot(); - // NRF_LOG_INFO("Slot %d tag type hf %d, lf %d", slot, slotConfig.group[slot].tag_hf, slotConfig.group[slot].tag_lf); - if (slotConfig.group[slot].tag_hf != TAG_TYPE_UNKNOWN) { - nfc_tag_14a_sense_switch(enable); + // NRF_LOG_INFO("Slot %d tag type hf %d, lf %d", slot, slotConfig.slots[slot].tag_hf, slotConfig.slots[slot].tag_lf); + if (enable && (slotConfig.slots[slot].enabled_hf) && (slotConfig.slots[slot].tag_hf != TAG_TYPE_UNDEFINED)) { + nfc_tag_14a_sense_switch(true); } else { nfc_tag_14a_sense_switch(false); } - if (slotConfig.group[slot].tag_lf != TAG_TYPE_UNKNOWN) { - lf_tag_125khz_sense_switch(enable); + if (enable && (slotConfig.slots[slot].enabled_lf) && (slotConfig.slots[slot].tag_lf != TAG_TYPE_UNDEFINED)) { + lf_tag_125khz_sense_switch(true); } else { lf_tag_125khz_sense_switch(false); } @@ -372,33 +390,116 @@ static void tag_emulation_sense_switch_all(bool enable) { * @param enable: Whether to enable this type of field induction */ void tag_emulation_sense_switch(tag_sense_type_t type, bool enable) { + uint8_t slot = tag_emulation_get_slot(); // Check the parameters, not allowed to switch non -normal field - if (type == TAG_SENSE_NO) APP_ERROR_CHECK(NRF_ERROR_INVALID_PARAM); - // Switch high frequency - if (type == TAG_SENSE_HF) nfc_tag_14a_sense_switch(enable); - // Switch low frequency - if (type == TAG_SENSE_LF) lf_tag_125khz_sense_switch(enable); + switch (type) { + case TAG_SENSE_NO: + APP_ERROR_CHECK(NRF_ERROR_INVALID_PARAM); + break; + case TAG_SENSE_HF: + if (enable && (slotConfig.slots[slot].enabled_hf) && + (slotConfig.slots[slot].tag_hf != TAG_TYPE_UNDEFINED)) { + nfc_tag_14a_sense_switch(true); + } else { + nfc_tag_14a_sense_switch(false); + } + break; + case TAG_SENSE_LF: + if (enable && (slotConfig.slots[slot].enabled_lf) && + (slotConfig.slots[slot].tag_lf != TAG_TYPE_UNDEFINED)) { + lf_tag_125khz_sense_switch(true); + } else { + lf_tag_125khz_sense_switch(false); + } + break; + } +} + + +static void tag_emulation_migrate_slot_config_v0_to_v8(void) { + // Copy old slotConfig content + uint8_t tmpbuf[sizeof(slotConfig)]; + memcpy(tmpbuf, (uint8_t *)&slotConfig, sizeof(tmpbuf)); + NRF_LOG_INFO("Migrating slotConfig v0..."); + NRF_LOG_HEXDUMP_INFO(tmpbuf, sizeof(tmpbuf)); + // Populate new slotConfig struct + slotConfig.version = TAG_SLOT_CONFIG_CURRENT_VERSION; + slotConfig.active_slot = tmpbuf[0]; + for (uint8_t i = 0; i < ARRAYLEN(slotConfig.slots); i++) { + bool enabled = tmpbuf[4 + (i * 4)] & 1; + + slotConfig.slots[i].tag_hf = tmpbuf[4 + (i * 4) + 2]; + for (uint8_t j = 0; j < ARRAYLEN(tag_specific_type_old2new_hf_values); j++) { + if (slotConfig.slots[i].tag_hf == tag_specific_type_old2new_hf_values[j][0]) { + slotConfig.slots[i].tag_hf = tag_specific_type_old2new_hf_values[j][1]; + } + } + slotConfig.slots[i].enabled_hf = slotConfig.slots[i].tag_hf != TAG_TYPE_UNDEFINED ? enabled : false; + NRF_LOG_INFO("Slot %i HF: %02X->%04X enabled:%i", i, tmpbuf[4 + (i * 4) + 2], slotConfig.slots[i].tag_hf, slotConfig.slots[i].enabled_hf); + + slotConfig.slots[i].tag_lf = tmpbuf[4 + (i * 4) + 3]; + for (uint8_t j = 0; j < ARRAYLEN(tag_specific_type_old2new_lf_values); j++) { + if (slotConfig.slots[i].tag_lf == tag_specific_type_old2new_lf_values[j][0]) { + slotConfig.slots[i].tag_lf = tag_specific_type_old2new_lf_values[j][1]; + } + } + slotConfig.slots[i].enabled_lf = slotConfig.slots[i].tag_lf != TAG_TYPE_UNDEFINED ? enabled : false; + NRF_LOG_INFO("Slot %i LF: %02X->%04X enabled:%i", i, tmpbuf[4 + (i * 4) + 3], slotConfig.slots[i].tag_lf, slotConfig.slots[i].enabled_lf); + } +} + + +static void tag_emulation_migrate_slot_config(void) { + switch (slotConfig.version) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + tag_emulation_migrate_slot_config_v0_to_v8(); + + /* + * Add new migration steps ABOVE THIS COMMENT + * `tag_emulation_save_config()` and `break` statements should only be used on the last migration step, all the previous steps must fall + * through to the next case. + */ + + tag_emulation_save_config(); + case TAG_SLOT_CONFIG_CURRENT_VERSION: + break; + default: + NRF_LOG_ERROR("Unsupported slotConfig migration attempted! (%d -> %d)", slotConfig.version, TAG_SLOT_CONFIG_CURRENT_VERSION); + break; + } } + /** * Load the emulated card configuration data, note that loading is just a card slot configuration */ -void tag_emulation_load_config(void) { +static void tag_emulation_load_config(void) { + uint16_t length = sizeof(slotConfig); // Read the card slot configuration data - bool ret = fds_read_sync(FDS_EMULATION_CONFIG_FILE_ID, FDS_EMULATION_CONFIG_RECORD_KEY, sizeof(slotConfig), (uint8_t *)&slotConfig); + bool ret = fds_read_sync(FDS_EMULATION_CONFIG_FILE_ID, FDS_EMULATION_CONFIG_RECORD_KEY, &length, (uint8_t *)&slotConfig); if (ret) { // After the reading is completed, we will save a BCC of the current configuration. When it is stored later, it can be used as a reference for the contrast between changes. calc_14a_crc_lut((uint8_t *)&slotConfig, sizeof(slotConfig), (uint8_t *)&m_slot_config_crc); NRF_LOG_INFO("Load tag slot config done."); + if (slotConfig.version < TAG_SLOT_CONFIG_CURRENT_VERSION) { // old slotConfig, need to migrate + tag_emulation_migrate_slot_config(); + } } else { - NRF_LOG_INFO("Tag slot config no exists."); + NRF_LOG_INFO("Tag slot config does not exist."); } } /** *Save the emulated card configuration data */ -void tag_emulation_save_config(void) { +static void tag_emulation_save_config(void) { // We are configured the card slot configuration, and we need to calculate the current card slot configuration CRC code to judge whether the data below is updated uint16_t new_calc_crc; calc_14a_crc_lut((uint8_t *)&slotConfig, sizeof(slotConfig), (uint8_t *)&new_calc_crc); @@ -452,14 +553,14 @@ void tag_emulation_save(void) { * Get the currently activated card slot index */ uint8_t tag_emulation_get_slot(void) { - return slotConfig.config.activated; + return slotConfig.active_slot; } /** * Set the currently activated card slot index */ void tag_emulation_set_slot(uint8_t index) { - slotConfig.config.activated = index; // Re -set to the new switched card slot + slotConfig.active_slot = index; // Re -set to the new switched card slot rgb_marquee_reset(); // force animation color refresh according to new slot } @@ -484,31 +585,52 @@ void tag_emulation_change_slot(uint8_t index, bool sense_disable) { /** * Determine whether the specified card slot is enabled */ -bool tag_emulation_slot_is_enable(uint8_t slot) { - //Return to the capacity of the corresponding card slot directly - return slotConfig.group[slot].enable; +bool tag_emulation_slot_is_enabled(uint8_t slot, tag_sense_type_t sense_type) { + switch (sense_type) { + case TAG_SENSE_LF: { + return slotConfig.slots[slot].enabled_lf; + break; + } + case TAG_SENSE_HF: { + return slotConfig.slots[slot].enabled_hf; + break; + } + default: + return false; + break; //Never happen + } } /** * Set whether the specified card slot is enabled */ -void tag_emulation_slot_set_enable(uint8_t slot, bool enable) { +void tag_emulation_slot_set_enable(uint8_t slot, tag_sense_type_t sense_type, bool enable) { //Set the capacity of the corresponding card slot directly - slotConfig.group[slot].enable = enable; + switch (sense_type) { + case TAG_SENSE_LF: { + slotConfig.slots[slot].enabled_lf = enable; + break; + } + case TAG_SENSE_HF: { + slotConfig.slots[slot].enabled_hf = enable; + break; + } + default: + break; //Never happen + } } /** *Find the next valid card slot */ uint8_t tag_emulation_slot_find_next(uint8_t slot_now) { - uint8_t start_slot = (slot_now + 1 >= TAG_MAX_SLOT_NUM) ? 0 : slot_now + 1; - for (uint8_t i = start_slot; i < sizeof(slotConfig.group);) { - if (i == slot_now) return slot_now; // No other activated card slots were found after a reincarnation - if (slotConfig.group[i].enable) return i; // Check whether the card slot that is currently traversed is enabled, so that the capacity determines that the current card slot is the card slot that can effectively enable capacity - if (i + 1 >= TAG_MAX_SLOT_NUM) { // Continue the next cycle + uint8_t start_slot = (slot_now + 1 == TAG_MAX_SLOT_NUM) ? 0 : slot_now + 1; + for (uint8_t i = start_slot;;) { + if (i == slot_now) return slot_now; // No other activated card slots were found after a loop + if (slotConfig.slots[i].enabled_hf || slotConfig.slots[i].enabled_lf) return i; // Check whether the card slot that is currently traversed is enabled, so that the capacity determines that the current card slot is the card slot that can effectively enable capacity + i++; + if (i == TAG_MAX_SLOT_NUM) { // Continue the next cycle i = 0; - } else { - i += 1; } } return slot_now; // If you cannot find it, the specified return value of the pass is returned by default @@ -518,14 +640,14 @@ uint8_t tag_emulation_slot_find_next(uint8_t slot_now) { * Find the previous valid card slot */ uint8_t tag_emulation_slot_find_prev(uint8_t slot_now) { - uint8_t start_slot = (slot_now - 1 < 0) ? (TAG_MAX_SLOT_NUM - 1) : slot_now - 1; - for (uint8_t i = start_slot; i < sizeof(slotConfig.group);) { - if (i == slot_now) return slot_now; //No other activated card slots were found after a reincarnation - if (slotConfig.group[i].enable) return i; // Check whether the card slot that is currently traversed is enabled, so that the capacity determines that the current card slot is the card slot that can effectively enable capacity - if (i - 1 < 0) { // Continue the next cycle - i = (TAG_MAX_SLOT_NUM - 1); + uint8_t start_slot = (slot_now == 0) ? (TAG_MAX_SLOT_NUM - 1) : slot_now - 1; + for (uint8_t i = start_slot;;) { + if (i == slot_now) return slot_now; //No other activated card slots were found after a loop + if (slotConfig.slots[i].enabled_hf || slotConfig.slots[i].enabled_lf) return i; // Check whether the card slot that is currently traversed is enabled, so that the capacity determines that the current card slot is the card slot that can effectively enable capacity + if (i == 0) { // Continue the next cycle + i = TAG_MAX_SLOT_NUM - 1; } else { - i -= 1; + i--; } } return slot_now; // If you cannot find it, the specified return value of the pass is returned by default @@ -539,11 +661,11 @@ void tag_emulation_change_type(uint8_t slot, tag_specific_type_t tag_type) { NRF_LOG_INFO("sense type = %d", sense_type); switch (sense_type) { case TAG_SENSE_LF: { - slotConfig.group[slot].tag_lf = tag_type; + slotConfig.slots[slot].tag_lf = tag_type; break; } case TAG_SENSE_HF: { - slotConfig.group[slot].tag_hf = tag_type; + slotConfig.slots[slot].tag_hf = tag_type; break; } default: @@ -564,34 +686,36 @@ void tag_emulation_change_type(uint8_t slot, tag_specific_type_t tag_type) { void tag_emulation_factory_init(void) { fds_slot_record_map_t map_info; - if (slotConfig.group[0].enable && slotConfig.group[0].tag_hf != TAG_TYPE_UNKNOWN && slotConfig.group[0].tag_lf != TAG_TYPE_UNKNOWN) { - // Initialized a dual -frequency card in the card slot, if there is no historical record, it is a new state of factory. + // Initialized a dual -frequency card in the card slot, if there is no historical record, it is a new state of factory. + if (slotConfig.slots[0].enabled_hf && slotConfig.slots[0].tag_hf == TAG_TYPE_MIFARE_1024) { + // Initialize a high -frequency M1 card in the card slot 1, if it does not exist. get_fds_map_by_slot_sense_type_for_dump(0, TAG_SENSE_HF, &map_info); - bool is_slot1_hf_data_exists = fds_is_exists(map_info.id, map_info.key); + if (!fds_is_exists(map_info.id, map_info.key)) { + tag_emulation_factory_data(0, slotConfig.slots[0].tag_hf); + } + } + + if (slotConfig.slots[0].enabled_lf && slotConfig.slots[0].tag_lf == TAG_TYPE_EM410X) { + // Initialize a low -frequency EM410X card in slot 1, if it does not exist. get_fds_map_by_slot_sense_type_for_dump(0, TAG_SENSE_LF, &map_info); - bool is_slot1_lf_data_exists = fds_is_exists(map_info.id, map_info.key); - // Here are no high -frequency cards and low -frequency cards of card slot 1 here. - if (!is_slot1_hf_data_exists && !is_slot1_lf_data_exists) { - tag_emulation_factory_data(0, slotConfig.group[0].tag_hf); - tag_emulation_factory_data(0, slotConfig.group[0].tag_lf); + if (!fds_is_exists(map_info.id, map_info.key)) { + tag_emulation_factory_data(0, slotConfig.slots[0].tag_lf); } } - if (slotConfig.group[1].enable && slotConfig.group[1].tag_hf != TAG_TYPE_UNKNOWN) { + if (slotConfig.slots[1].enabled_hf && slotConfig.slots[1].tag_hf == TAG_TYPE_MIFARE_1024) { // Initialize a high -frequency M1 card in the card slot 2, if it does not exist. get_fds_map_by_slot_sense_type_for_dump(1, TAG_SENSE_HF, &map_info); - bool is_slot2_hf_data_exists = fds_is_exists(map_info.id, map_info.key); - if (!is_slot2_hf_data_exists) { - tag_emulation_factory_data(1, slotConfig.group[1].tag_hf); + if (!fds_is_exists(map_info.id, map_info.key)) { + tag_emulation_factory_data(1, slotConfig.slots[1].tag_hf); } } - if (slotConfig.group[2].enable && slotConfig.group[2].tag_lf != TAG_TYPE_UNKNOWN) { + if (slotConfig.slots[2].enabled_lf && slotConfig.slots[2].tag_lf == TAG_TYPE_EM410X) { // Initialize a low -frequency EM410X card in slot 3, if it does not exist. get_fds_map_by_slot_sense_type_for_dump(2, TAG_SENSE_LF, &map_info); - bool is_slot3_lf_data_exists = fds_is_exists(map_info.id, map_info.key); - if (!is_slot3_lf_data_exists) { - tag_emulation_factory_data(2, slotConfig.group[2].tag_lf); + if (!fds_is_exists(map_info.id, map_info.key)) { + tag_emulation_factory_data(2, slotConfig.slots[2].tag_lf); } } } diff --git a/firmware/application/src/rfid/nfctag/tag_emulation.h b/firmware/application/src/rfid/nfctag/tag_emulation.h index d583cd75..f17a9c5b 100644 --- a/firmware/application/src/rfid/nfctag/tag_emulation.h +++ b/firmware/application/src/rfid/nfctag/tag_emulation.h @@ -4,7 +4,7 @@ #include #include #include - +#include "app_util.h" #include "utils.h" #include "tag_base_type.h" @@ -44,26 +44,34 @@ typedef struct { * This configuration can be preserved by persistently to Flash * 4 bytes a word, keep in mind the entire word alignment */ -typedef struct ALIGN_U32 { - //Basic configuration - struct { - uint8_t activated; //Which card slot is currently activated (which card slot is used) - uint8_t reserved1; // reserve - uint8_t reserved2; //reserve - uint8_t reserved3; // reserve - } config; - // The configuration of each card slot itself - struct { - //Basic configuration, occupying two bytes - uint8_t enable: 1; // Whether to enable the card - uint8_t reserved1: 7; // reserve - uint8_t reserved2; //reserve - // Specific type of simulation card - tag_specific_type_t tag_hf; - tag_specific_type_t tag_lf; - } group[TAG_MAX_SLOT_NUM]; -} tag_slot_config_t; +#define TAG_SLOT_CONFIG_CURRENT_VERSION 8 +// Intended struct size, for static assert +#define TAG_SLOT_CONFIG_CURRENT_SIZE 68 +typedef struct { + //Basic configuration + uint8_t version; // struct version (U8 so map on old .activated<=7 field) + uint8_t active_slot; // Which slot is currently active + uint32_t : 0; // U32 align + struct { // 4-byte slot config + 2*2-byte tag_specific_types + // Individual slot configuration + uint32_t enabled_hf : 1; // Whether to enable the HF card + uint32_t enabled_lf : 1; // Whether to enable the LF card + uint32_t : 0; // U32 align + // Specific type of emulated card + union { + uint16_t U16_tag_hf; + tag_specific_type_t tag_hf; + }; + union { + uint16_t U16_tag_lf; + tag_specific_type_t tag_lf; + }; + } slots[TAG_MAX_SLOT_NUM]; +} PACKED tag_slot_config_t; + +// Use the macro to check the struct size +STATIC_ASSERT(sizeof(tag_slot_config_t) == TAG_SLOT_CONFIG_CURRENT_SIZE); // The most basic simulation card initialization program void tag_emulation_init(void); @@ -95,16 +103,17 @@ uint8_t tag_emulation_get_slot(void); // Switch the card slot to control whether the passing parameter control is closed during the switching period to listen to void tag_emulation_change_slot(uint8_t index, bool sense_disable); // Get the card slot to enable the state -bool tag_emulation_slot_is_enable(uint8_t slot); +bool tag_emulation_slot_is_enabled(uint8_t slot, tag_sense_type_t sense_type); // Set the card slot to enable -void tag_emulation_slot_set_enable(uint8_t slot, bool enable); +void tag_emulation_slot_set_enable(uint8_t slot, tag_sense_type_t sense_type, bool enable); // Get the simulation card type of the corresponding card slot -void tag_emulation_get_specific_type_by_slot(uint8_t slot, tag_specific_type_t tag_type[2]); +void tag_emulation_get_specific_types_by_slot(uint8_t slot, tag_slot_specific_type_t *tag_types); // Initialize some factory data void tag_emulation_factory_init(void); //In the direction, query any card slot that enable uint8_t tag_emulation_slot_find_next(uint8_t slot_now); uint8_t tag_emulation_slot_find_prev(uint8_t slot_now); +bool is_tag_specific_type_valid(tag_specific_type_t tag_type); #endif diff --git a/firmware/application/src/rfid/reader/hf/mf1_toolbox.c b/firmware/application/src/rfid/reader/hf/mf1_toolbox.c index f2c50684..98858b8f 100644 --- a/firmware/application/src/rfid/reader/hf/mf1_toolbox.c +++ b/firmware/application/src/rfid/reader/hf/mf1_toolbox.c @@ -9,10 +9,13 @@ #include "nrf_log.h" #include "nrf_log_ctrl.h" #include "nrf_log_default_backends.h" - +#include "bsp_wdt.h" +#include "hw_connect.h" +#include "nrf_gpio.h" +#include "rgb_marquee.h" // The default delay of the antenna reset -static uint32_t g_ant_reset_delay = 8; +static uint32_t g_ant_reset_delay = 100; // Label information used for global operations static picc_14a_tag_t m_tag_info; @@ -27,7 +30,7 @@ static picc_14a_tag_t *p_tag_info = &m_tag_info; * @retval : none * */ -void nonce_distance_notable(uint32_t *msb, uint32_t *lsb) { +static void nonce_distance(uint32_t *msb, uint32_t *lsb) { uint16_t x = 1, pos; uint8_t calc_ok = 0; @@ -61,12 +64,12 @@ void nonce_distance_notable(uint32_t *msb, uint32_t *lsb) { * false = hardened prng * */ -bool validate_prng_nonce_notable(uint32_t nonce) { +static bool check_lfsr_prng(uint32_t nonce) { // Give the initial coordinate value uint32_t msb = nonce >> 16; uint32_t lsb = nonce & 0xffff; //The coordinates are passed in direct operation, and the rumors are also indirectly spread by the passing parameters. - nonce_distance_notable(&msb, &lsb); + nonce_distance(&msb, &lsb); return ((65535 - msb + lsb) % 65535) == 16; } @@ -91,7 +94,7 @@ static inline void reset_radio_field_with_delay(void) { * @retval : The length of the card response data, this length is the length of the bit, not the length of Byte * */ -uint8_t send_cmd(struct Crypto1State *pcs, uint8_t encrypted, uint8_t cmd, uint8_t data, uint8_t *status, uint8_t *answer, uint8_t *answer_parity, uint16_t answer_max_bit) { +static uint8_t send_cmd(struct Crypto1State *pcs, uint8_t encrypted, uint8_t cmd, uint8_t data, uint8_t *status, uint8_t *answer, uint8_t *answer_parity, uint16_t answer_max_bit) { // Here we set directly to static static uint8_t pos; static uint16_t len; @@ -256,7 +259,7 @@ int authex(struct Crypto1State *pcs, uint32_t uid, uint8_t blockNo, uint8_t keyT * As a result, the CPU cannot complete the replay attack within the same time. * There is basically no solution in this situation. It is recommended to close the non -critical interruption and task scheduling outside this code */ -uint8_t darkside_select_nonces(picc_14a_tag_t *tag, uint8_t block, uint8_t keytype, uint32_t *nt) { +static uint8_t darkside_select_nonces(picc_14a_tag_t *tag, uint8_t block, uint8_t keytype, uint32_t *nt, mf1_darkside_status_t *darkside_status) { #define NT_COUNT 15 uint8_t tag_auth[4] = { keytype, block, 0x00, 0x00 }; uint8_t tag_resp[4] = { 0x00 }; @@ -269,6 +272,8 @@ uint8_t darkside_select_nonces(picc_14a_tag_t *tag, uint8_t block, uint8_t keyty //Random number collection for (i = 0; i < NT_COUNT; i++) { + bsp_wdt_feed(); + while (NRF_LOG_PROCESS()); //When the antenna is reset, we must make sure // 1. The antenna is powered off for a long time to ensure that the card is completely powered off, otherwise the pseudo -random number generator of the card cannot be reset // 2. Moderate power -off time, don't be too long, it will affect efficiency, and don't be too short. @@ -287,7 +292,7 @@ uint8_t darkside_select_nonces(picc_14a_tag_t *tag, uint8_t block, uint8_t keyty // Converted to the type of U32 and cache nt_list[i] = bytes_to_num(tag_resp, 4); // The byte array of the conversion response is 10 in NT - // NRF_LOG_INFO("Get nt: %"PRIu32"\r\n", nt_list[i]); + // NRF_LOG_INFO("Get nt: %"PRIu32, nt_list[i]); } // Take the random number @@ -315,12 +320,14 @@ uint8_t darkside_select_nonces(picc_14a_tag_t *tag, uint8_t block, uint8_t keyty // If it is not greater than 0, it means that the clock cannot be synchronized. if (max == 0) { NRF_LOG_INFO("Can't sync nt.\n"); - return DARKSIDE_CANT_FIXED_NT; + *darkside_status = DARKSIDE_CANT_FIX_NT; + return HF_TAG_OK; } // NT is fixed successfully, the one with the highest number of times we take out // NRF_LOG_INFO("Sync nt: %"PRIu32", max = %d\n", nt_list[m], max); if (nt) *nt = nt_list[m]; // Only when the caller needs to get NT + *darkside_status = DARKSIDE_OK; return HF_TAG_OK; } @@ -332,7 +339,8 @@ uint8_t darkside_select_nonces(picc_14a_tag_t *tag, uint8_t block, uint8_t keyty * */ uint8_t darkside_recover_key(uint8_t targetBlk, uint8_t targetTyp, - uint8_t firstRecover, uint8_t ntSyncMax, DarksideCore *dc) { + uint8_t firstRecover, uint8_t ntSyncMax, DarksideCore_t *dc, + mf1_darkside_status_t *darkside_status) { // Card information for fixed use static uint32_t uid_ori = 0; @@ -363,7 +371,7 @@ uint8_t darkside_recover_key(uint8_t targetBlk, uint8_t targetTyp, uint8_t status = 0x00; // This variable is responsible for saving card communication status uint16_t len = 0x00; // This variable is responsible for saving the data of the card in the communication process to respond to the length of the card uint8_t nt_diff = 0x00; // This variable is critical, don't initialize it, because the following is used directly - + bool led_toggle = false; // We need to confirm the use of a certain card first if (pcd_14a_reader_scan_auto(p_tag_info) == HF_TAG_OK) { uid_cur = get_u32_tag_uid(p_tag_info); @@ -373,6 +381,12 @@ uint8_t darkside_recover_key(uint8_t targetBlk, uint8_t targetTyp, // Verification instructions need to add CRC16 crc_14a_append(tag_auth, 2); + rgb_marquee_stop(); + set_slot_light_color(RGB_GREEN); + uint32_t *led_pins = hw_get_led_array(); + for (uint8_t i = 0; i < RGB_LIST_NUM; i++) { + nrf_gpio_pin_clear(led_pins[i]); + } // Initialize the static variable if it is the first attack if (firstRecover) { @@ -385,8 +399,8 @@ uint8_t darkside_recover_key(uint8_t targetBlk, uint8_t targetTyp, uid_ori = get_u32_tag_uid(p_tag_info); // Then you need to fix a random number that may appear - status = darkside_select_nonces(p_tag_info, targetBlk, targetTyp, &nt_ori); - if (status != HF_TAG_OK) { + status = darkside_select_nonces(p_tag_info, targetBlk, targetTyp, &nt_ori, darkside_status); + if ((status != HF_TAG_OK) || (*darkside_status != DARKSIDE_OK)) { //The fixed random number failed, and the next step cannot be performed return status; } @@ -398,12 +412,21 @@ uint8_t darkside_recover_key(uint8_t targetBlk, uint8_t targetTyp, par = par_low; if (uid_ori != uid_cur) { - return DARKSIDE_TAG_CHANGED; + *darkside_status = DARKSIDE_TAG_CHANGED; + return HF_TAG_OK; } } - // Always collect different NACK under a large cycle do { + bsp_wdt_feed(); + while (NRF_LOG_PROCESS()); + // update LEDs + led_toggle ^= 1; + if (led_toggle) { + nrf_gpio_pin_set(led_pins[nt_diff]); + } else { + nrf_gpio_pin_clear(led_pins[nt_diff]); + } // Reset the receiving sign of NACK received_nack = 0; @@ -428,7 +451,7 @@ uint8_t darkside_recover_key(uint8_t targetBlk, uint8_t targetTyp, //The byte array of the conversion response is 10 in NT nt_cur = bytes_to_num(dat_recv, 4); - // NRF_LOG_INFO("Get nt: %"PRIu32"\r\n", nt_cur); + //NRF_LOG_INFO("Get nt: %"PRIu32, nt_cur); //Determine the clock synchronization (fixing NT) if (nt_cur != nt_ori) { @@ -436,8 +459,9 @@ uint8_t darkside_recover_key(uint8_t targetBlk, uint8_t targetTyp, // And the random number we chose was also successfully attacked // In other words, this error has no chance to correct the random number if (++resync_count == ntSyncMax) { - NRF_LOG_INFO("Can't fix nonce.\r\n"); - return DARKSIDE_CANT_FIXED_NT; + NRF_LOG_INFO("Can't fix nonce."); + *darkside_status = DARKSIDE_CANT_FIX_NT; + return HF_TAG_OK; } // When the clock is not synchronized, the following operation is meaningless @@ -446,19 +470,20 @@ uint8_t darkside_recover_key(uint8_t targetBlk, uint8_t targetTyp, // NRF_LOG_INFO("Sync nt -> nt_fix: %"PRIu32", nt_new: %"PRIu32"\r\n", nt_ori, nt_cur); continue; } - //Originally, we only need to send PAR, and use every bit of them as a school test. // But the sending function we implemented only supports one UINT8_T, that is, a byte as a bit // Therefore, we need to replace the communication writing of PM3 into our. // There are not many times here anyway, we directly expand the code for conversion - par_byte[0] = par >> 0 & 0x1; - par_byte[1] = par >> 1 & 0x1; - par_byte[2] = par >> 2 & 0x1; - par_byte[3] = par >> 3 & 0x1; - par_byte[4] = par >> 4 & 0x1; - par_byte[5] = par >> 5 & 0x1; - par_byte[6] = par >> 6 & 0x1; - par_byte[7] = par >> 7 & 0x1; + par_byte[0] = par >> 7 & 0x1; + par_byte[1] = par >> 6 & 0x1; + par_byte[2] = par >> 5 & 0x1; + par_byte[3] = par >> 4 & 0x1; + par_byte[4] = par >> 3 & 0x1; + par_byte[5] = par >> 2 & 0x1; + par_byte[6] = par >> 1 & 0x1; + par_byte[7] = par >> 0 & 0x1; + + //NRF_LOG_INFO("DBG step%i par=%x", nt_diff, par); len = 0; pcd_14a_reader_bits_transfer(mf_nr_ar, 64, par_byte, dat_recv, par_byte, &len, U8ARR_BIT_LEN(dat_recv)); @@ -467,17 +492,19 @@ uint8_t darkside_recover_key(uint8_t targetBlk, uint8_t targetTyp, resync_count = 0; if (len == 4) { - // NRF_LOG_INFO("NACK get: 0x%x\r\n", receivedAnswer[0]); + NRF_LOG_INFO("NACK acquired (%i/8)", nt_diff + 1); received_nack = 1; } else if (len == 32) { // did we get lucky and got our dummy key to be valid? // however we dont feed key w uid it the prng.. NRF_LOG_INFO("Auth Ok, you are so lucky!\n"); - return DARKSIDE_LUCK_AUTH_OK; + *darkside_status = DARKSIDE_LUCKY_AUTH_OK; + return HF_TAG_OK; } // Receive answer. This will be a 4 Bit NACK when the 8 parity bits are OK after decoding if (received_nack) { + nrf_gpio_pin_set(led_pins[nt_diff]); if (nt_diff == 0) { // there is no need to check all parities for other nt_diff. Parity Bits for mf_nr_ar[0..2] won't change par_low = par & 0xE0; @@ -500,11 +527,15 @@ uint8_t darkside_recover_key(uint8_t targetBlk, uint8_t targetTyp, par++; if (par == 0) { // tried all 256 possible parities without success. Card doesn't send NACK. NRF_LOG_INFO("Card doesn't send NACK.\r\n"); - return DARKSIDE_NACK_NO_SEND; + *darkside_status = DARKSIDE_NO_NAK_SENT; + return HF_TAG_OK; } } else { - // Why this? - par = ((par & 0x1F) + 1) | par_low; + par = ((par + 1) & 0x1F) | par_low; + if (par == par_low) { // tried all 32 possible parities without success. Got some NACK but not all 8... + NRF_LOG_INFO("Card sent only %i/8 NACK.", nt_diff); + return DARKSIDE_NO_NAK_SENT; + } } } } while (1); @@ -522,6 +553,7 @@ uint8_t darkside_recover_key(uint8_t targetBlk, uint8_t targetTyp, memcpy(dc->ar, mf_nr_ar + 4, sizeof(dc->ar)); // NRF_LOG_INFO("Darkside done!\n"); + *darkside_status = DARKSIDE_OK; return HF_TAG_OK; } @@ -537,23 +569,6 @@ void antenna_switch_delay(uint32_t delay_ms) { g_ant_reset_delay = delay_ms; } -/** -* @brief :Determine whether this card supports DARKSIDE attack -* @retval : If support, return hf_tag_ok, if it is not supported, -* Return to the results of abnormal results during the detection process: -* 1. DARKSIDE_CANT_FIXED_NT -* 2. DARKSIDE_NACK_NO_SEND -* 3. DARKSIDE_TAG_CHANGED -* Or other card -related communication errors, the most common is loss card HF_TAG_NO -* -*/ -uint8_t check_darkside_support() { - // Instantiated parameter - DarksideCore dc; - //Determine and return the result directly - return darkside_recover_key(0x03, PICC_AUTHENT1A, true, 0x15, &dc); -} - /** * @brief : Determine whether this card supports M1 verification steps * @retval : If support, it will return hf_tag_ok, @@ -579,7 +594,7 @@ uint8_t check_tag_response_nt(picc_14a_tag_t *tag, uint32_t *nt) { // Send instructions and get NT return *nt = send_cmd(pcs, AUTH_FIRST, PICC_AUTHENT1A, 0x03, &status, dat_recv, par_recv, U8ARR_BIT_LEN(dat_recv)); if (*nt != 32) { - // dbg_block_printf("No 32 data recv on send_cmd: %d\n", *nt); + // NRF_LOG_INFO("No 32 data recv on send_cmd: %d\n", *nt); return HF_ERR_STAT; } *nt = bytes_to_num(dat_recv, 4); @@ -594,7 +609,7 @@ uint8_t check_tag_response_nt(picc_14a_tag_t *tag, uint32_t *nt) { * Lost card hf_tag_no and wrong status hf_errstat * */ -uint8_t check_std_mifare_nt_support() { +uint8_t check_std_mifare_nt_support(void) { uint32_t nt1 = 0; // Find card, search on the field @@ -602,7 +617,7 @@ uint8_t check_std_mifare_nt_support() { return HF_TAG_NO; } - // Get NT + // Get NT and return status return check_tag_response_nt(p_tag_info, &nt1); } @@ -612,7 +627,7 @@ uint8_t check_std_mifare_nt_support() { * If support, return nested_tag_is_static, if not support, * */ -uint8_t check_static_nested_support() { +uint8_t check_static_prng(bool *is_static) { uint32_t nt1, nt2; uint8_t status; @@ -639,29 +654,31 @@ uint8_t check_static_nested_support() { } // Detect whether the random number is static - if (nt1 == nt2) { - return NESTED_TAG_IS_STATIC; - } - + *is_static = (nt1 == nt2); return HF_TAG_OK; } /** -* @brief : Determine whether this card supports the most common, weaker, and easiest nested attack +* @brief : Determine whether this card supports the most common, weaker, and easiest nested attack, or static, or hardnested * @retval : critical result * */ -uint8_t check_weak_nested_support() { +uint8_t check_prng_type(mf1_prng_type_t *prng_type) { uint8_t status; uint32_t nt1; - status = check_static_nested_support(); + bool is_static; + status = check_static_prng(&is_static); // If the judgment process is found, it is found that the StaticNested detection cannot be completed // Then return the state directly, no need to perform the following judgment logic. if (status != HF_TAG_OK) { return status; } + if (is_static) { + *prng_type = PRNG_STATIC; + return HF_TAG_OK; + } // Non -Static card, you can continue to run down logic // ------------------------------------ @@ -682,17 +699,19 @@ uint8_t check_weak_nested_support() { } //Calculate the effectiveness of NT - if (validate_prng_nonce_notable(nt1)) { + if (check_lfsr_prng(nt1)) { // NRF_LOG_INFO("The tag support Nested\n"); - return HF_TAG_OK; + *prng_type = PRNG_WEAK; + } else { + // NRF_LOG_INFO("The tag support HardNested\n"); + // NT is unpredictable and invalid. + *prng_type = PRNG_HARD; } - // NRF_LOG_INFO("The tag support HardNested\n"); - // NT is unpredictable and invalid. // ------------------------------------ // end - return NESTED_TAG_IS_HARD; + return HF_TAG_OK; } /** @@ -702,12 +721,12 @@ uint8_t check_weak_nested_support() { * @retval : Distance value * */ -uint32_t measure_nonces(uint32_t from, uint32_t to) { +static uint32_t measure_nonces(uint32_t from, uint32_t to) { // Give the initial coordinate value uint32_t msb = from >> 16; uint32_t lsb = to >> 16; // The coordinates are passed in direct operation, and the rumors are also indirectly spread by the passing parameters. - nonce_distance_notable(&msb, &lsb); + nonce_distance(&msb, &lsb); return (65535 + lsb - msb) % 65535; } @@ -753,7 +772,7 @@ uint32_t measure_median(uint32_t *src, uint32_t length) { * @retval : Operating result * */ -uint8_t measure_distance(uint64_t u64Key, uint8_t block, uint8_t type, uint32_t *distance) { +static uint8_t measure_distance(uint64_t u64Key, uint8_t block, uint8_t type, uint32_t *distance) { struct Crypto1State mpcs = {0, 0}; struct Crypto1State *pcs = &mpcs; uint32_t distances[DIST_NR] = { 0x00 }; @@ -784,11 +803,12 @@ uint8_t measure_distance(uint64_t u64Key, uint8_t block, uint8_t type, uint32_t // If you really encounter the same NT, then it can only be explained that this card is a ST card for special firmware if (nt1 == nt2) { NRF_LOG_INFO("StaticNested: %08x vs %08x\n", nt1, nt2); - return NESTED_TAG_IS_STATIC; + *distance = 0; + return HF_TAG_OK; } // After the measurement is completed, store in the buffer distances[index++] = measure_nonces(nt1, nt2); - // dbg_block_printf("dist = %"PRIu32"\n\n", distances[index - 1]); + // NRF_LOG_INFO("dist = %"PRIu32"\n\n", distances[index - 1]); } while (index < DIST_NR); //The final calculation of the distance between the two NTs and spread it directly @@ -808,7 +828,7 @@ uint8_t measure_distance(uint64_t u64Key, uint8_t block, uint8_t type, uint32_t * @retval : Successfully return hf_tag_ok, verify the unsuccessful return of the non -hf_tag_ok value * */ -uint8_t nested_recover_core(NestedCore *pnc, uint64_t keyKnown, uint8_t blkKnown, uint8_t typKnown, uint8_t targetBlock, uint8_t targetType) { +static uint8_t nested_recover_core(mf1_nested_core_t *pnc, uint64_t keyKnown, uint8_t blkKnown, uint8_t typKnown, uint8_t targetBlock, uint8_t targetType) { struct Crypto1State mpcs = {0, 0}; struct Crypto1State *pcs = &mpcs; uint8_t status; @@ -845,16 +865,16 @@ uint8_t nested_recover_core(NestedCore *pnc, uint64_t keyKnown, uint8_t blkKnown /** * @brief :NESTED is implemented by default to collect random numbers of the sets_nr group. This function is only responsible for collecting, not responsible for conversion and analysis as KS -* @param :ncs : Nested core structure array, save related communication data * @param :keyKnown : The U64 value of the known secret key of the card * @param :blkKnown :The owner of the known secret key of the card * @param :typKnown : Types of the known secret key of the card, 0x60 (A secret) or 0x61 (B secret) * @param :targetBlock : The target sector that requires a Nested attack * @param :targetType : The target key type requires the Nested attack -* @retval :The attack returns hf_tag_ok, the attack is unsuccessful to return the non -hf_tag_ok value +* @param :ncs : Nested core structure array, save related communication data +* @retval :The attack success return HF_TAG_OK, else return the error code * */ -uint8_t nested_recover_key(uint64_t keyKnown, uint8_t blkKnown, uint8_t typKnown, uint8_t targetBlock, uint8_t targetType, NestedCore ncs[SETS_NR]) { +uint8_t nested_recover_key(uint64_t keyKnown, uint8_t blkKnown, uint8_t typKnown, uint8_t targetBlock, uint8_t targetType, mf1_nested_core_t ncs[SETS_NR]) { uint8_t m, res; // all operations must be based on the card res = pcd_14a_reader_scan_auto(p_tag_info); @@ -887,29 +907,96 @@ uint8_t nested_recover_key(uint64_t keyKnown, uint8_t blkKnown, uint8_t typKnown * @retval : Operating status value * */ -uint8_t nested_distance_detect(uint8_t block, uint8_t type, uint8_t *key, NestedDist *nd) { +uint8_t nested_distance_detect(uint8_t block, uint8_t type, uint8_t *key, uint8_t *uid, uint32_t *distance) { uint8_t status = HF_TAG_OK; - uint32_t distance = 0; + *distance = 0; //Must ensure that there is a card on the court status = pcd_14a_reader_scan_auto(p_tag_info); if (status != HF_TAG_OK) { return status; } else { // At least the card exists, you can copy the UID to the buffer first - get_4byte_tag_uid(p_tag_info, nd->uid); + get_4byte_tag_uid(p_tag_info, uid); } // Get distance, prepare for the next attack - status = measure_distance( - bytes_to_num(key, 6), - block, - type, - &distance - ); - // Everything is normal, we need to put the distance value into the result - if (status == HF_TAG_OK) { - num_to_bytes(distance, 4, nd->distance); - } - return status; + return measure_distance(bytes_to_num(key, 6), block, type, distance); +} + +/** +* @brief : StaticNested core, used to collect NT. +* This function is only responsible for collection and is not responsible for converting and parsing to KS. +* @param :p_nt1 : NT1, non encrypted. +* @param :p_nt2 : NT2, encrypted. +* @param :keyKnown : U64 value of the known key of the card +* @param :blkKnown : The sector to which the card's known secret key belongs +* @param :typKnown : The known key type of the card, 0x60 (A key) or 0x61 (B key) +* @param :targetBlock : Target sectors that require nested attacks +* @param :targetType : Target key types that require nested attacks +* @param :nestedAgain : StaticNested enhanced vulnerability, which can obtain two sets of encrypted random numbers based on nested verification of known keys +* @retval : Successfully collected and returned to HF_TAG_OK, otherwise an error code will be returned. +* +*/ +uint8_t static_nested_recover_core(uint8_t *p_nt1, uint8_t *p_nt2, uint64_t keyKnown, uint8_t blkKnown, uint8_t typKnown, uint8_t targetBlock, uint8_t targetType, uint8_t nestedAgain) { + struct Crypto1State mpcs = {0, 0}; + struct Crypto1State *pcs = &mpcs; + uint8_t status, len; + uint8_t parity[4] = {0x00}; + uint8_t answer[4] = {0x00}; + uint32_t uid, nt1, nt2; + uid = get_u32_tag_uid(p_tag_info); + pcd_14a_reader_halt_tag(); + if (pcd_14a_reader_scan_auto(p_tag_info) != HF_TAG_OK) { + return HF_TAG_NO; + } + status = authex(pcs, uid, blkKnown, typKnown, keyKnown, AUTH_FIRST, &nt1); + if (status != HF_TAG_OK) { + return MF_ERR_AUTH; + } + if (nestedAgain) { + status = authex(pcs, uid, blkKnown, typKnown, keyKnown, AUTH_NESTED, NULL); + if (status != HF_TAG_OK) { + return MF_ERR_AUTH; + } + } + len = send_cmd(pcs, AUTH_NESTED, targetType, targetBlock, &status, answer, parity, U8ARR_BIT_LEN(answer)); + if (len != 32) { + NRF_LOG_INFO("No 32 data recv on sendcmd: %d\r\n", len); + return HF_ERR_STAT; + } + nt2 = bytes_to_num(answer, 4); + num_to_bytes(nt1, 4, p_nt1); + num_to_bytes(nt2, 4, p_nt2); + return HF_TAG_OK; +} + +/** +* @brief : StaticNested encapsulates and calls the functions implemented by the core to collect 2 sets of random numbers. +* This function is only responsible for collection and is not responsible for converting and parsing to KS. +* @param :keyKnown : U64 value of the known key of the card +* @param :blkKnown : The sector to which the card's known secret key belongs +* @param :typKnown : The known key type of the card, 0x60 (A key) or 0x61 (B key) +* @param :targetBlock : Target sectors that require nested attacks +* @param :targetType : Target key type that require nested attacks +* @param :sncs : StaticNested Decrypting Core Structure Array +* @retval : Successfully collected and returned to HF_TAG_OK, otherwise an error code will be returned. +* +*/ +uint8_t static_nested_recover_key(uint64_t keyKnown, uint8_t blkKnown, uint8_t typKnown, uint8_t targetBlock, uint8_t targetType, mf1_static_nested_core_t *sncs) { + uint8_t res; + res = pcd_14a_reader_scan_auto(p_tag_info); + if (res != HF_TAG_OK) { + return res; + } + get_4byte_tag_uid(p_tag_info, sncs->uid); + res = static_nested_recover_core(sncs->core[0].nt1, sncs->core[0].nt2, keyKnown, blkKnown, typKnown, targetBlock, targetType, false); + if (res != HF_TAG_OK) { + return res; + } + res = static_nested_recover_core(sncs->core[1].nt1, sncs->core[1].nt2, keyKnown, blkKnown, typKnown, targetBlock, targetType, true); + if (res != HF_TAG_OK) { + return res; + } + return HF_TAG_OK; } /** diff --git a/firmware/application/src/rfid/reader/hf/mf1_toolbox.h b/firmware/application/src/rfid/reader/hf/mf1_toolbox.h index 01f86795..48dfb7cd 100644 --- a/firmware/application/src/rfid/reader/hf/mf1_toolbox.h +++ b/firmware/application/src/rfid/reader/hf/mf1_toolbox.h @@ -6,7 +6,8 @@ #include #include #include - +#include +#include "netdata.h" #define SETS_NR 2 // Using several sets of random number probes, at least two can ensure that there are two sets of random number combinations for intersection inquiries. The larger the value, the easier it is to succeed. #define DIST_NR 3 // The more distance the distance can accurately judge the communication stability of the current card @@ -18,18 +19,35 @@ #define AUTH_FIRST 0 #define AUTH_NESTED 2 -typedef struct { //Answer the distance parameters required for Nested attack - uint8_t uid[4]; //The U32 part of the UID part of this distance data - uint8_t distance[4]; //Unblocked explicitly random number -} NestedDist; - +typedef enum { + PRNG_STATIC = 0u, // the random number of the card response is fixed + PRNG_WEAK = 1u, // the random number of the card response is weak + PRNG_HARD = 2u, // the random number of the card response is unpredictable +} mf1_prng_type_t; typedef struct { //Answer the random number parameters required for Nested attack uint8_t nt1[4]; //Unblocked explicitly random number uint8_t nt2[4]; //Random number of nested verification encryption uint8_t par; //The puppet test of the communication process of nested verification encryption, only the "low 3 digits', that is, the right 3 -} NestedCore; +} mf1_nested_core_t; + +typedef struct { + uint8_t uid[4]; + struct { + uint8_t nt1[4]; + uint8_t nt2[4]; + } core[2]; +} mf1_static_nested_core_t; + +typedef enum { + DARKSIDE_OK = 0u, // normal process + DARKSIDE_CANT_FIX_NT = 1u, // the random number cannot be fixed, this situation may appear on some UID card + DARKSIDE_LUCKY_AUTH_OK = 2u, // the direct authentification is successful, maybe the key is just the default one + DARKSIDE_NO_NAK_SENT = 3u, // the card does not respond to NACK, it may be a card that fixes Nack logic vulnerabilities + DARKSIDE_TAG_CHANGED = 4u, // card change while running DARKSIDE +} mf1_darkside_status_t; +// this struct is also used in the fw/cli protocol, therefore PACKED typedef struct { uint8_t uid[4]; uint8_t nt[4]; @@ -37,7 +55,7 @@ typedef struct { uint8_t ks_list[8]; uint8_t nr[4]; uint8_t ar[4]; -} DarksideCore; +} PACKED DarksideCore_t; #ifdef __cplusplus @@ -50,25 +68,30 @@ uint8_t darkside_recover_key( uint8_t targetTyp, uint8_t firstRecover, uint8_t ntSyncMax, - DarksideCore *dc + DarksideCore_t *dc, + mf1_darkside_status_t *darkside_status ); + uint8_t nested_distance_detect( uint8_t block, uint8_t type, uint8_t *key, - NestedDist *nd -); -uint8_t nested_recover_key( - uint64_t keyKnown, - uint8_t blkKnown, - uint8_t typKnown, - uint8_t targetBlock, - uint8_t targetType, - NestedCore ncs[SETS_NR] + uint8_t *uid, + uint32_t *distance ); -uint8_t check_darkside_support(void); -uint8_t check_weak_nested_support(void); -uint8_t check_std_mifare_nt_support(void); + +#define NESTED_CORE_PARAM_DEF \ + uint64_t keyKnown, \ + uint8_t blkKnown, \ + uint8_t typKnown, \ + uint8_t targetBlock, \ + uint8_t targetType \ + +uint8_t nested_recover_key(NESTED_CORE_PARAM_DEF, mf1_nested_core_t ncs[SETS_NR]); +uint8_t static_nested_recover_key(NESTED_CORE_PARAM_DEF, mf1_static_nested_core_t *sncs); + +uint8_t check_prng_type(mf1_prng_type_t *type); +uint8_t check_std_mifare_nt_support(); void antenna_switch_delay(uint32_t delay_ms); uint8_t auth_key_use_522_hw(uint8_t block, uint8_t type, uint8_t *key); diff --git a/firmware/application/src/rfid/reader/hf/rc522.c b/firmware/application/src/rfid/reader/hf/rc522.c index 29d3c27c..087234e5 100644 --- a/firmware/application/src/rfid/reader/hf/rc522.c +++ b/firmware/application/src/rfid/reader/hf/rc522.c @@ -24,6 +24,7 @@ NRF_LOG_MODULE_REGISTER(); #define RC522_DOSEL nrf_gpio_pin_clear(HF_SPI_SELECT) #define RC522_UNSEL nrf_gpio_pin_set(HF_SPI_SELECT) +bool g_is_reader_antenna_on = false; //CRC 14A calculator, when the MCU performance is too weak, or when the MCU is busy, you can use 522 to calculate CRC static uint8_t m_crc_computer = 0; @@ -196,9 +197,11 @@ void pcd_14a_reader_reset(void) { write_register_single(CommandReg, PCD_IDLE); write_register_single(CommandReg, PCD_RESET); - // Switch antenna - clear_register_mask(TxControlReg, 0x03); - set_register_mask(TxControlReg, 0x03); + bsp_delay_ms(10); + + // Then default does not allow the antenna + // Please don't continue to make high -frequency antennas + pcd_14a_reader_antenna_off(); // Disable the timer of 522, use the MCU timer timeout time write_register_single(TModeReg, 0x00); @@ -208,9 +211,7 @@ void pcd_14a_reader_reset(void) { // Define common mode and receive common mode and receiveMiFare cartoon communication, CRC initial value 0x6363 write_register_single(ModeReg, 0x3D); - // Then default does not allow the antenna - // Please don't continue to make high -frequency antennas - pcd_14a_reader_antenna_off(); + bsp_delay_ms(10); } } @@ -491,6 +492,7 @@ uint8_t pcd_14a_reader_scan_once(picc_14a_tag_t *tag) { if (tag) { tag->uid_len = 0; memset(tag->uid, 0, 10); + tag->ats_len = 0; } else { return STATUS_PAR_ERR; // Finding cards are not allowed to be transmitted to the label information structure } @@ -579,6 +581,30 @@ uint8_t pcd_14a_reader_scan_once(picc_14a_tag_t *tag) { // Therefore + 1 tag->cascade = cascade_level + 1; } + if (tag->sak & 0x20) { + // Tag supports 14443-4, sending RATS + uint16_t ats_size; + status = pcd_14a_reader_ats_request(tag->ats, &ats_size, 0xFF * 8); + ats_size -= 2; // size returned by pcd_14a_reader_ats_request includes CRC + if (ats_size > 254) { + NRF_LOG_INFO("Invalid ATS > 254!"); + return HF_ERR_ATS; + } + tag->ats_len = ats_size; + // We do not validate ATS here as we want to report ATS as it is without breaking 14a scan + if (tag->ats[0] != ats_size - 1) { + NRF_LOG_INFO("Invalid ATS! First byte doesn't match received length"); + // return HF_ERR_ATS; + } + if (status != HF_TAG_OK) { + NRF_LOG_INFO("Tag SAK claimed to support ATS but tag NAKd RATS"); + // return HF_ERR_ATS; + } + /* + * FIXME: If there is an issue here, it will cause the label to lose its selected state. + * It is necessary to reselect the card after the issue occurs here. + */ + } return HF_TAG_OK; } @@ -619,6 +645,7 @@ uint8_t pcd_14a_reader_ats_request(uint8_t *pAts, uint16_t *szAts, uint16_t szAt status = pcd_14a_reader_bytes_transfer(PCD_TRANSCEIVE, rats, sizeof(rats), pAts, szAts, szAtsBitMax); if (status != HF_TAG_OK) { + *szAts = 0; NRF_LOG_INFO("Err at ats receive.\n"); return status; } @@ -945,6 +972,8 @@ void pcd_14a_reader_calc_crc(uint8_t *pbtData, size_t szLen, uint8_t *pbtCrc) { */ inline void pcd_14a_reader_antenna_on(void) { set_register_mask(TxControlReg, 0x03); + g_is_reader_antenna_on = true; + TAG_FIELD_LED_ON(); } /** @@ -952,6 +981,8 @@ inline void pcd_14a_reader_antenna_on(void) { */ inline void pcd_14a_reader_antenna_off(void) { clear_register_mask(TxControlReg, 0x03); + g_is_reader_antenna_on = false; + TAG_FIELD_LED_OFF(); } /** @@ -1096,3 +1127,140 @@ inline void crc_14a_append(uint8_t *pbtData, size_t szLen) { inline void pcd_14a_reader_crc_computer(uint8_t use522CalcCRC) { m_crc_computer = use522CalcCRC; } + +/** +* @brief : The hf 14a raw command implementation function can be used to send the 14A command with the specified configuration parameters. +* @param :waitResp : Wait for tag response +* @param :appendCrc : Do you want to add CRC before sending +* @param :autoSelect : Automatically select card before sending data +* @param :keepField : Do you want to keep the RF field on after sending +* @param :checkCrc : Is CRC verified after receiving data? If CRC verification is enabled, CRC bytes will be automatically removed after verification is completed. +* @param :waitRespTimeout : If waitResp is enabled, this parameter will be the timeout value to wait for the tag to respond +* @param :szDataSend : The number of bytes or bits of data to be sent +* @param :pDataSend : Pointer to the buffer of the data to be sent +* +* @retval : Execution Status +* +*/ +uint8_t pcd_14a_reader_raw_cmd(bool openRFField, bool waitResp, bool appendCrc, bool autoSelect, bool keepField, bool checkCrc, uint16_t waitRespTimeout, + uint16_t szDataSendBits, uint8_t *pDataSend, uint8_t *pDataRecv, uint16_t *pszDataRecv, uint16_t szDataRecvBitMax) { + // Status code, default is OK. + uint8_t status = HF_TAG_OK; + // Reset recv length. + *pszDataRecv = 0; + + // If additional CRC is required, first add the CRC to the tail. + if (appendCrc) { + if (szDataSendBits == 0) { + NRF_LOG_INFO("Adding CRC but missing data"); + return STATUS_PAR_ERR; + } + if (szDataSendBits % 8) { + NRF_LOG_INFO("Adding CRC incompatible with partial bytes"); + return STATUS_PAR_ERR; + } + if (szDataSendBits > ((DEF_FIFO_LENGTH - DEF_CRC_LENGTH) * 8)) { + // Note: Adding CRC requires at least two bytes of free space. If the transmitted data is already greater than or equal to 64, an error needs to be returned + NRF_LOG_INFO("Adding CRC requires data length less than or equal to 62."); + return STATUS_PAR_ERR; + } + // Calculate and append CRC byte data to the buffer + crc_14a_append(pDataSend, szDataSendBits / 8); + // CRC is also sent as part of the data, so the total length needs to be added to the CRC length here + szDataSendBits += DEF_CRC_LENGTH * 8; + } + + if (autoSelect || szDataSendBits) { + // override openRFField if we need to select or to send data + openRFField = true; + } + if (openRFField && ! g_is_reader_antenna_on) { // Open rf field? + pcd_14a_reader_reset(); + pcd_14a_reader_antenna_on(); + bsp_delay_ms(8); + } + + if (autoSelect) { + picc_14a_tag_t ti; + status = pcd_14a_reader_scan_once(&ti); + // Determine whether the card search was successful + if (status != HF_TAG_OK) { + pcd_14a_reader_antenna_off(); + return status; + } + } + + // Is there any data that needs to be sent + if (szDataSendBits) { + // If there is no need to receive data, the data receiving cache needs to be empty, otherwise a specified timeout value needs to be set + // Caching old timeout values + uint16_t oldWaitRespTimeout = g_com_timeout_ms; + if (waitResp) { + // Then set the new values in + g_com_timeout_ms = waitRespTimeout; + } else { + pDataRecv = NULL; + } + if (szDataSendBits % 8) { + status = pcd_14a_reader_bits_transfer( + pDataSend, + szDataSendBits, + NULL, + pDataRecv, + NULL, + pszDataRecv, + szDataRecvBitMax + ); + } else { + status = pcd_14a_reader_bytes_transfer( + PCD_TRANSCEIVE, + pDataSend, + szDataSendBits / 8, + pDataRecv, + pszDataRecv, + szDataRecvBitMax + ); + } + + // If we need to receive data, we need to perform further operations on the data based on the remaining configuration after receiving it + if (waitResp) { + // Number of bits to bytes + uint8_t finalRecvBytes = (*pszDataRecv / 8) + (*pszDataRecv % 8 > 0 ? 1 : 0); + // If CRC verification is required, we need to perform CRC calculation + if (checkCrc) { + if (finalRecvBytes >= 3) { // Ensure at least three bytes (one byte of data+two bytes of CRC) + // Calculate and store CRC + uint8_t crc_buff[DEF_CRC_LENGTH] = { 0x00 }; + crc_14a_calculate(pDataRecv, finalRecvBytes - DEF_CRC_LENGTH, crc_buff); + // Verify CRC + if (pDataRecv[finalRecvBytes - 2] != crc_buff[0] || pDataRecv[finalRecvBytes - 1] != crc_buff[1]) { + // We have found an error in CRC verification and need to inform the upper computer! + *pszDataRecv = 0; + status = HF_ERR_CRC; + } else { + // If the CRC needs to be verified by the device and the device determines that the CRC is normal, + // we will return the data without CRC + *pszDataRecv = finalRecvBytes - DEF_CRC_LENGTH; + } + } else { + // The data is insufficient to support the length of the CRC, so it is returned as is + *pszDataRecv = 0; + } + } else { + // Do not verify CRC, all data is returned as is + *pszDataRecv = finalRecvBytes; + } + // We need to recover the timeout value + g_com_timeout_ms = oldWaitRespTimeout; + } else { + *pszDataRecv = 0; + } + } + + // Finally, keep the field open as needed + if (!keepField) { + pcd_14a_reader_antenna_off(); + } + + return status; +} diff --git a/firmware/application/src/rfid/reader/hf/rc522.h b/firmware/application/src/rfid/reader/hf/rc522.h index be1493f7..e3842d20 100644 --- a/firmware/application/src/rfid/reader/hf/rc522.h +++ b/firmware/application/src/rfid/reader/hf/rc522.h @@ -6,6 +6,7 @@ #include #include #include +#include "netdata.h" /* * rC522CommandWord @@ -151,13 +152,16 @@ #define U8ARR_BIT_LEN(src) ((sizeof(src)) * (8)) // basicStructurePackagingOfLabelInformation +// this struct is also used in the fw/cli protocol, therefore PACKED typedef struct { uint8_t uid[10]; // theByteArrayOfTheCardNumber,TheLongest10Byte uint8_t uid_len; // theLengthOfTheCardNumber uint8_t cascade; // theAntiCollisionLevelValueIs1Representation 4Byte,2Represents7Byte,3Means10Byte uint8_t sak; // chooseToConfirm uint8_t atqa[2]; // requestResponse -} picc_14a_tag_t; + uint8_t ats[0xFF];// 14443-4 answer to select + uint8_t ats_len; // 14443-4 answer to select size +} PACKED picc_14a_tag_t; #ifdef __cplusplus extern "C" { @@ -216,6 +220,9 @@ uint8_t pcd_14a_reader_mf1_read(uint8_t addr, uint8_t *pData); uint8_t pcd_14a_reader_halt_tag(void); void pcd_14a_reader_fast_halt_tag(void); +uint8_t pcd_14a_reader_raw_cmd(bool openRFField, bool waitResp, bool appendCrc, bool autoSelect, bool keepField, bool checkCrc, uint16_t waitRespTimeout, + uint16_t szDataSendBits, uint8_t *pDataSend, uint8_t *pDataRecv, uint16_t *pszDataRecv, uint16_t szDataRecvBitMax); + // UID & UFUID tag operation uint8_t pcd_14a_reader_gen1a_unlock(void); uint8_t pcd_14a_reader_gen1a_uplock(void); diff --git a/firmware/application/src/rfid_main.c b/firmware/application/src/rfid_main.c index 83fb273f..d6861bf5 100644 --- a/firmware/application/src/rfid_main.c +++ b/firmware/application/src/rfid_main.c @@ -94,11 +94,11 @@ device_mode_t get_device_mode(void) { * @return uint8_t Color 0R, 1G, 2B */ uint8_t get_color_by_slot(uint8_t slot) { - tag_specific_type_t tag_type[2]; - tag_emulation_get_specific_type_by_slot(slot, tag_type); - if (tag_type[0] != TAG_TYPE_UNKNOWN && tag_type[1] != TAG_TYPE_UNKNOWN) { + tag_slot_specific_type_t tag_types; + tag_emulation_get_specific_types_by_slot(slot, &tag_types); + if (tag_types.tag_hf != TAG_TYPE_UNDEFINED && tag_types.tag_lf != TAG_TYPE_UNDEFINED) { return 0; // Dual -frequency card simulation, return R, indicate a dual -frequency card - } else if (tag_type[0] != TAG_TYPE_UNKNOWN) { //High -frequency simulation, return G + } else if (tag_types.tag_hf != TAG_TYPE_UNDEFINED) { //High -frequency simulation, return G return 1; } else { // Low -frequency simulation, return B return 2; diff --git a/firmware/application/src/rgb_marquee.c b/firmware/application/src/rgb_marquee.c index f4aece9a..42416683 100644 --- a/firmware/application/src/rgb_marquee.c +++ b/firmware/application/src/rgb_marquee.c @@ -33,6 +33,7 @@ nrf_drv_pwm_config_t pwm_config = {//PWM configuration structure }; static autotimer *timer; static uint8_t ledblink6_step = 0; +static uint8_t ledblink6_color = RGB_RED; static uint8_t ledblink1_step = 0; extern bool g_usb_led_marquee_enable; @@ -392,7 +393,7 @@ void ledblink6(void) { } if (ledblink6_step == 0) { - set_slot_light_color(0); + set_slot_light_color(ledblink6_color); for (uint8_t i = 0; i < RGB_LIST_NUM; i++) { nrf_gpio_pin_clear(led_array[i]); } @@ -420,7 +421,7 @@ void ledblink6(void) { pwm_sequ_val.channel_2 = pwm_sequ_val.channel_0; pwm_sequ_val.channel_3 = pwm_sequ_val.channel_0; nrfx_pwm_uninit(&pwm0_ins); //Close PWM output - set_slot_light_color(1); + set_slot_light_color(ledblink6_color); nrf_drv_pwm_init(&pwm0_ins, &pwm_config, ledblink6_pwm_callback); nrf_drv_pwm_simple_playback(&pwm0_ins, &seq, 1, NRF_DRV_PWM_FLAG_LOOP); ledblink6_step = 3; @@ -457,7 +458,7 @@ void ledblink6(void) { pwm_sequ_val.channel_2 = pwm_sequ_val.channel_0; pwm_sequ_val.channel_3 = pwm_sequ_val.channel_0; nrfx_pwm_uninit(&pwm0_ins); //Close PWM output - set_slot_light_color(1); + set_slot_light_color(ledblink6_color); nrf_drv_pwm_init(&pwm0_ins, &pwm_config, ledblink6_pwm_callback); nrf_drv_pwm_simple_playback(&pwm0_ins, &seq, 1, NRF_DRV_PWM_FLAG_LOOP); ledblink6_step = 7; @@ -477,6 +478,10 @@ void ledblink6(void) { } } else { ledblink6_step = 0; + //if (++ledblink6_color == RGB_WHITE) ledblink6_color = RGB_RED; + uint8_t new_color = rand() % 6; + for (; new_color == ledblink6_color; new_color = rand() % 6); + ledblink6_color = new_color; } } } diff --git a/firmware/application/src/settings.c b/firmware/application/src/settings.c index ddf72d96..159d54a8 100644 --- a/firmware/application/src/settings.c +++ b/firmware/application/src/settings.c @@ -44,7 +44,7 @@ void settings_init_button_long_press_config(void) { // add on version4 void settings_init_ble_connect_key_config(void) { - uint8_t p_key_u8[] = DEFAULT_BLE_CONNECT_KEY; + uint8_t p_key_u8[] = DEFAULT_BLE_PAIRING_KEY; settings_set_ble_connect_key(p_key_u8); } @@ -95,7 +95,8 @@ void settings_migrate(void) { } void settings_load_config(void) { - bool ret = fds_read_sync(FDS_SETTINGS_FILE_ID, FDS_SETTINGS_RECORD_KEY, sizeof(config), (uint8_t *)&config); + uint16_t length = sizeof(config); + bool ret = fds_read_sync(FDS_SETTINGS_FILE_ID, FDS_SETTINGS_RECORD_KEY, &length, (uint8_t *)&config); if (ret) { NRF_LOG_INFO("Load config done."); // After the reading is complete, we first save a copy of the current CRC, which can be used as a reference for comparison of changes when saving later @@ -276,7 +277,7 @@ uint8_t *settings_get_ble_connect_key(void) { * @param key Ble connect key for your device */ void settings_set_ble_connect_key(uint8_t *key) { - memcpy(config.ble_connect_key, key, BLE_CONNECT_KEY_LEN_MAX); + memcpy(config.ble_connect_key, key, BLE_PAIRING_KEY_LEN); } void settings_set_ble_pairing_enable(bool enable) { diff --git a/firmware/application/src/settings.h b/firmware/application/src/settings.h index 1a26bf17..adfd3cd1 100644 --- a/firmware/application/src/settings.h +++ b/firmware/application/src/settings.h @@ -6,8 +6,8 @@ #include "utils.h" #define SETTINGS_CURRENT_VERSION 5 -#define BLE_CONNECT_KEY_LEN_MAX 6 -#define DEFAULT_BLE_CONNECT_KEY "123456" // length must == 6 +#define BLE_PAIRING_KEY_LEN 6 +#define DEFAULT_BLE_PAIRING_KEY "123456" // length must == 6 typedef enum { SettingsAnimationModeFull = 0U, diff --git a/firmware/application/src/utils/dataframe.c b/firmware/application/src/utils/dataframe.c index 0b5c9619..15a088e6 100644 --- a/firmware/application/src/utils/dataframe.c +++ b/firmware/application/src/utils/dataframe.c @@ -1,6 +1,5 @@ #include "dataframe.h" -#include "hex_utils.h" - +#include "netdata.h" #define NRF_LOG_MODULE_NAME data_frame #include "nrf_log.h" @@ -8,40 +7,26 @@ #include "nrf_log_default_backends.h" NRF_LOG_MODULE_REGISTER(); - -/* - * ********************************************************************************************************************************* - * Variable length data frame format - * Designed by proxgrind - * Date: 20221205 - * - * 0 1 2 3 45 6 7 8 8 + n 8 + n + 1 - * SOF(1byte) LRC(1byte) CMD(2byte) Status(2byte) Data Length(2byte) Frame Head LRC(1byte) Data(length) Frame All LRC(1byte) - * 0x11 0xEF cmd(u16) status(u16) length(u16) lrc(u8) data(u8*) lrc(u8) - * - * The data length max is 512, frame length max is 1 + 1 + 2 + 2 + 2 + 1 + n + 1 = (10 + n) - * So, one frame will than 10 byte. - * ********************************************************************************************************************************* - */ - -#define DATA_PACK_TRANSMISSION_ON 0x11 -#define DATA_LRC_CUT(val) ((uint8_t)(0x100 - val)) - - -static uint8_t m_data_rx_buffer[DATA_PACK_MAX_DATA_LENGTH + DATA_PACK_BASE_LENGTH]; -static uint8_t m_data_tx_buffer[DATA_PACK_MAX_DATA_LENGTH + DATA_PACK_BASE_LENGTH]; +static netdata_frame_raw_t m_netdata_frame_rx_buf; +static netdata_frame_raw_t m_netdata_frame_tx_buf; +static data_frame_tx_t m_frame_tx_buf_info = { + .buffer = (uint8_t *) &m_netdata_frame_tx_buf, // default buffer +}; static uint16_t m_data_rx_position = 0; -static uint8_t m_data_rx_lrc = 0; static uint16_t m_data_cmd; static uint16_t m_data_status; static uint16_t m_data_len; static uint8_t *m_data_buffer; static volatile bool m_data_completed = false; static data_frame_cbk_t m_frame_process_cbk = NULL; -static data_frame_tx_t m_frame_tx_buf_info = { - .buffer = m_data_tx_buffer, // default buffer -}; +static uint8_t compute_lrc(uint8_t *buf, uint16_t bufsize) { + uint8_t lrc = 0x00; + for (uint16_t i = 0; i < bufsize; i++) { + lrc += buf[i]; + } + return 0x100 - lrc; +} /** * @brief: create a packet, put the created data packet into the buffer, and wait for the post to set up a non busy state @@ -50,38 +35,41 @@ static data_frame_tx_t m_frame_tx_buf_info = { * @param length: answerDataLength * @param data: answerData */ -data_frame_tx_t *data_frame_make(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - uint8_t lrc_tx = 0x00; - uint16_t i, j; +data_frame_tx_t *data_frame_make(uint16_t cmd, uint16_t status, uint16_t data_length, uint8_t *data) { + if (data_length > 0 && data == NULL) { + NRF_LOG_ERROR("data_frame_make error, null pointer."); + return NULL; + } + if (data_length > 512) { + NRF_LOG_ERROR("data_frame_make error, too much data."); + return NULL; + } + NRF_LOG_INFO("TX Data frame: cmd = 0x%04x (%i), status = 0x%04x, length = %d%s", cmd, cmd, status, data_length, data_length > 0 ? ", data =" : ""); + if (data_length > 0) { + NRF_LOG_HEXDUMP_INFO(data, data_length); + } + + netdata_frame_postamble_t *tx_post = (netdata_frame_postamble_t *)((uint8_t *)&m_netdata_frame_tx_buf + sizeof(netdata_frame_preamble_t) + data_length); // sof - m_frame_tx_buf_info.buffer[0] = DATA_PACK_TRANSMISSION_ON; + m_netdata_frame_tx_buf.pre.sof = NETDATA_FRAME_SOF; // sof lrc - lrc_tx += m_frame_tx_buf_info.buffer[0]; - m_frame_tx_buf_info.buffer[1] = DATA_LRC_CUT(lrc_tx); - lrc_tx += m_frame_tx_buf_info.buffer[1]; + m_netdata_frame_tx_buf.pre.lrc1 = compute_lrc((uint8_t *)&m_netdata_frame_tx_buf.pre, offsetof(netdata_frame_preamble_t, lrc1)); // cmd - num_to_bytes(cmd, 2, &m_frame_tx_buf_info.buffer[2]); + m_netdata_frame_tx_buf.pre.cmd = U16HTONS(cmd); // status - num_to_bytes(status, 2, &m_frame_tx_buf_info.buffer[4]); - // length - num_to_bytes(length, 2, &m_frame_tx_buf_info.buffer[6]); + m_netdata_frame_tx_buf.pre.status = U16HTONS(status); + // data_length + m_netdata_frame_tx_buf.pre.len = U16HTONS(data_length); // head lrc - for (i = 2; i < 8; i++) { - lrc_tx += m_frame_tx_buf_info.buffer[i]; - } - m_frame_tx_buf_info.buffer[8] = DATA_LRC_CUT(lrc_tx); - lrc_tx += m_frame_tx_buf_info.buffer[8]; + m_netdata_frame_tx_buf.pre.lrc2 = compute_lrc((uint8_t *)&m_netdata_frame_tx_buf.pre, offsetof(netdata_frame_preamble_t, lrc2)); // data - if (length > 0 && data != NULL) { - for (i = 9, j = 0; j < length; i++, j++) { - m_frame_tx_buf_info.buffer[i] = data[j]; - lrc_tx += m_frame_tx_buf_info.buffer[i]; - } + if (data_length > 0) { + memcpy(&m_netdata_frame_tx_buf.data, data, data_length); } // length out. - m_frame_tx_buf_info.length = (length + DATA_PACK_BASE_LENGTH); + m_frame_tx_buf_info.length = (sizeof(netdata_frame_preamble_t) + data_length + sizeof(netdata_frame_postamble_t)); // data all lrc - m_data_tx_buffer[m_frame_tx_buf_info.length - 1] = DATA_LRC_CUT(lrc_tx);; + tx_post->lrc3 = compute_lrc((uint8_t *)&m_netdata_frame_tx_buf.data, data_length); return (&m_frame_tx_buf_info); } @@ -90,7 +78,6 @@ data_frame_tx_t *data_frame_make(uint16_t cmd, uint16_t status, uint16_t length, */ void data_frame_reset(void) { m_data_rx_position = 0; - m_data_rx_lrc = 0; } /** @@ -105,7 +92,7 @@ void data_frame_receive(uint8_t *data, uint16_t length) { return; } // buffer overflow - if (m_data_rx_position + length >= sizeof(m_data_rx_buffer)) { + if (m_data_rx_position + length >= sizeof(m_netdata_frame_rx_buf)) { NRF_LOG_ERROR("Data frame wait overflow."); data_frame_reset(); return; @@ -113,50 +100,52 @@ void data_frame_receive(uint8_t *data, uint16_t length) { // frame process for (int i = 0; i < length; i++) { // copy to buffer - m_data_rx_buffer[m_data_rx_position] = data[i]; - if (m_data_rx_position < 2) { // start of frame - if (m_data_rx_position == 0) { - if (m_data_rx_buffer[m_data_rx_position] != DATA_PACK_TRANSMISSION_ON) { - // not sof byte - NRF_LOG_ERROR("Data frame no sof byte."); - data_frame_reset(); - return; - } + ((uint8_t *)(&m_netdata_frame_rx_buf))[m_data_rx_position] = data[i]; + if (m_data_rx_position == offsetof(netdata_frame_preamble_t, sof)) { + if (m_netdata_frame_rx_buf.pre.sof != NETDATA_FRAME_SOF) { + // not sof byte + NRF_LOG_ERROR("Data frame no sof byte."); + data_frame_reset(); + return; } - if (m_data_rx_position == 1) { - if (m_data_rx_buffer[m_data_rx_position] != DATA_LRC_CUT(m_data_rx_lrc)) { - // not sof lrc byte - NRF_LOG_ERROR("Data frame sof lrc error."); - data_frame_reset(); - return; - } + } else if (m_data_rx_position == offsetof(netdata_frame_preamble_t, lrc1)) { + if (m_netdata_frame_rx_buf.pre.lrc1 != compute_lrc((uint8_t *)&m_netdata_frame_rx_buf.pre, offsetof(netdata_frame_preamble_t, lrc1))) { + // not sof lrc byte + NRF_LOG_ERROR("Data frame sof lrc error."); + data_frame_reset(); + return; } - } else if (m_data_rx_position == 8) { // frame head lrc - if (m_data_rx_buffer[m_data_rx_position] != DATA_LRC_CUT(m_data_rx_lrc)) { + } else if (m_data_rx_position == offsetof(netdata_frame_preamble_t, lrc2)) { // frame head lrc + if (m_netdata_frame_rx_buf.pre.lrc2 != compute_lrc((uint8_t *)&m_netdata_frame_rx_buf.pre, offsetof(netdata_frame_preamble_t, lrc2))) { // frame head lrc error NRF_LOG_ERROR("Data frame head lrc error."); data_frame_reset(); return; } // frame head complete, cache info - m_data_cmd = bytes_to_num(&m_data_rx_buffer[2], 2); - m_data_status = bytes_to_num(&m_data_rx_buffer[4], 2); - m_data_len = bytes_to_num(&m_data_rx_buffer[6], 2); + m_data_cmd = U16NTOHS(m_netdata_frame_rx_buf.pre.cmd); + m_data_status = U16NTOHS(m_netdata_frame_rx_buf.pre.status); + m_data_len = U16NTOHS(m_netdata_frame_rx_buf.pre.len); NRF_LOG_INFO("Data frame data length %d.", m_data_len); // check data length - if (m_data_len > DATA_PACK_MAX_DATA_LENGTH) { - NRF_LOG_ERROR("Data frame data length too than of max."); + if (m_data_len > NETDATA_MAX_DATA_LENGTH) { + NRF_LOG_ERROR("Data frame data length larger than max."); data_frame_reset(); return; } - } else if (m_data_rx_position > 8) { // frame data + } else if (m_data_rx_position >= offsetof(netdata_frame_raw_t, data)) { // frame data // check all data ready. - if (m_data_rx_position == (8 + m_data_len + 1)) { - if (m_data_rx_buffer[m_data_rx_position] == DATA_LRC_CUT(m_data_rx_lrc)) { + if (m_data_rx_position == (sizeof(netdata_frame_preamble_t) + m_data_len)) { + netdata_frame_postamble_t *rx_post = (netdata_frame_postamble_t *)((uint8_t *)&m_netdata_frame_rx_buf + sizeof(netdata_frame_preamble_t) + m_data_len); + if (rx_post->lrc3 == compute_lrc((uint8_t *)&m_netdata_frame_rx_buf.data, m_data_len)) { // ok, lrc for data is check success. // and we are receive completed - m_data_buffer = m_data_len > 0 ? &m_data_rx_buffer[9] : NULL; + m_data_buffer = m_data_len > 0 ? (uint8_t *)&m_netdata_frame_rx_buf.data : NULL; m_data_completed = true; + NRF_LOG_INFO("RX Data frame: cmd = 0x%04x (%i), status = 0x%04x, length = %d%s", m_data_cmd, m_data_cmd, m_data_status, m_data_len, m_data_len > 0 ? ", data =" : ""); + if (m_data_len > 0) { + NRF_LOG_HEXDUMP_INFO(m_data_buffer, m_data_len); + } } else { // data frame lrc error NRF_LOG_ERROR("Data frame finally lrc error."); @@ -165,8 +154,6 @@ void data_frame_receive(uint8_t *data, uint16_t length) { return; } } - // calculate lrc - m_data_rx_lrc += data[i]; // index update m_data_rx_position++; } diff --git a/firmware/application/src/utils/dataframe.h b/firmware/application/src/utils/dataframe.h index d0e22f5d..87dba08b 100644 --- a/firmware/application/src/utils/dataframe.h +++ b/firmware/application/src/utils/dataframe.h @@ -1,23 +1,18 @@ -#ifndef DATA_PACK_H -#define DATA_PACK_H +#ifndef DATAFRAME_H +#define DATAFRAME_H #include #include - -#define DATA_PACK_MAX_DATA_LENGTH 512 -#define DATA_PACK_BASE_LENGTH 10 - - // Data frame process callback typedef void (*data_frame_cbk_t)(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data); + // TX buffer typedef struct { uint8_t *const buffer; uint16_t length; } data_frame_tx_t; - void data_frame_receive(uint8_t *data, uint16_t length); void data_frame_process(void); void on_data_frame_complete(data_frame_cbk_t callback); @@ -30,4 +25,4 @@ data_frame_tx_t *data_frame_make( ); -#endif +#endif // DATAFRAME_H diff --git a/firmware/application/src/utils/fds_util.c b/firmware/application/src/utils/fds_util.c index 9edc639d..8de52c7e 100644 --- a/firmware/application/src/utils/fds_util.c +++ b/firmware/application/src/utils/fds_util.c @@ -49,26 +49,30 @@ bool fds_is_exists(uint16_t id, uint16_t key) { /** *Read record + * Length: set it to max length (size of buffer) + * After execution, length is updated to the real flash record size */ -bool fds_read_sync(uint16_t id, uint16_t key, uint16_t max_length, uint8_t *buffer) { +bool fds_read_sync(uint16_t id, uint16_t key, uint16_t *length, uint8_t *buffer) { ret_code_t err_code; //The results of the operation fds_flash_record_t flash_record; // Pointing to the actual information in Flash fds_record_desc_t record_desc; // Recorded handle if (fds_find_record(id, key, &record_desc)) { err_code = fds_record_open(&record_desc, &flash_record); //Open the record so that it is marked as the open state APP_ERROR_CHECK(err_code); - if (flash_record.p_header->length_words * 4 <= max_length) { // Read the data in Flash here to the given RAM + if (flash_record.p_header->length_words * 4 <= *length) { // Read the data in Flash here to the given RAM // Make sure that the buffer will not overflow, read this record memcpy(buffer, flash_record.p_data, flash_record.p_header->length_words * 4); NRF_LOG_INFO("FDS read success."); + *length = flash_record.p_header->length_words * 4; return true; } else { - NRF_LOG_INFO("FDS buffer too small, can't run memcpy, fds size = %d, buffer size = %d", flash_record.p_header->length_words * 4, max_length); + NRF_LOG_INFO("FDS buffer too small, can't run memcpy, fds size = %d, buffer size = %d", flash_record.p_header->length_words * 4, *length); } err_code = fds_record_close(&record_desc); // Close the file after the operation is completed APP_ERROR_CHECK(err_code); } //If the correct data is not loaded, this record may not exist + *length = 0; return false; } diff --git a/firmware/application/src/utils/fds_util.h b/firmware/application/src/utils/fds_util.h index feb477a0..76909487 100644 --- a/firmware/application/src/utils/fds_util.h +++ b/firmware/application/src/utils/fds_util.h @@ -4,7 +4,7 @@ #include "fds.h" -bool fds_read_sync(uint16_t id, uint16_t key, uint16_t max_length, uint8_t *buffer); +bool fds_read_sync(uint16_t id, uint16_t key, uint16_t *length, uint8_t *buffer); bool fds_write_sync(uint16_t id, uint16_t key, uint16_t data_length_words, void *buffer); int fds_delete_sync(uint16_t id, uint16_t key); bool fds_is_exists(uint16_t id, uint16_t key); diff --git a/firmware/application/src/utils/netdata.h b/firmware/application/src/utils/netdata.h new file mode 100644 index 00000000..37390f1d --- /dev/null +++ b/firmware/application/src/utils/netdata.h @@ -0,0 +1,51 @@ +#ifndef NETDATA_H +#define NETDATA_H + +#include +#include +#include "utils.h" + +#define NETDATA_MAX_DATA_LENGTH 512 + +/* + * ********************************************************************************************************************************* + * Variable length data frame format + * Designed by proxgrind + * Date: 20221205 + * + * 0 1 2 3 45 6 7 8 8 + n 8 + n + 1 + * SOF(1byte) LRC(1byte) CMD(2byte) Status(2byte) Data Length(2byte) Frame Head LRC(1byte) Data(length) Frame All LRC(1byte) + * 0x11 0xEF cmd(u16) status(u16) length(u16) lrc(u8) data(u8*) lrc(u8) + * + * The data length max is 512, frame length is 1 + 1 + 2 + 2 + 2 + 1 + n + 1 = (10 + n) + * So, one frame will be between 10 and 522 bytes. + * ********************************************************************************************************************************* + */ + +// data frame preamble as sent from/to the client, Network byte order. + +typedef struct { + uint8_t sof; + uint8_t lrc1; + uint16_t cmd; + uint16_t status; + uint16_t len; + uint8_t lrc2; +} PACKED netdata_frame_preamble_t; + +#define NETDATA_FRAME_SOF 0x11 + +typedef struct { + uint8_t lrc3; +} PACKED netdata_frame_postamble_t; + +// For reception and CRC check +typedef struct { + netdata_frame_preamble_t pre; + uint8_t data[NETDATA_MAX_DATA_LENGTH]; + netdata_frame_postamble_t foopost; // Probably not at that offset! +} PACKED netdata_frame_raw_t; + +// Command-specific structs are defined in their respective cmd_processor handlers in app_cmd.c + +#endif /* NETDATA_H */ diff --git a/firmware/application/src/utils/syssleep.h b/firmware/application/src/utils/syssleep.h index af3349b5..4e1e99c1 100644 --- a/firmware/application/src/utils/syssleep.h +++ b/firmware/application/src/utils/syssleep.h @@ -4,7 +4,7 @@ #include // Wake up equipment -#define SLEEP_DELAY_MS_BUTTON_WAKEUP 4000 // The sleep delay of the button awakened +#define SLEEP_DELAY_MS_BUTTON_WAKEUP 8000 // The sleep delay of the button awakened #define SLEEP_DELAY_MS_FIELD_WAKEUP 4000 // The sleep delay (including high and low frequencies) of the field wake -up (including high and low frequency) #define SLEEP_DELAY_MS_FIRST_POWER 1000 // The sleep delay of the first power supply (access to the battery) diff --git a/firmware/common/hw_connect.c b/firmware/common/hw_connect.c index bb86bd54..a4249c51 100644 --- a/firmware/common/hw_connect.c +++ b/firmware/common/hw_connect.c @@ -107,9 +107,6 @@ void hw_connect_init(void) { #if defined(PROJECT_CHAMELEON_ULTRA) if (m_hw_ver == 1) { LED_FIELD = (NRF_GPIO_PIN_MAP(1, 1)); - LED_R = (NRF_GPIO_PIN_MAP(0, 24)); - LED_G = (NRF_GPIO_PIN_MAP(0, 22)); - LED_B = (NRF_GPIO_PIN_MAP(1, 0)); LED_1 = (NRF_GPIO_PIN_MAP(0, 20)); LED_2 = (NRF_GPIO_PIN_MAP(0, 17)); LED_3 = (NRF_GPIO_PIN_MAP(0, 15)); @@ -118,15 +115,26 @@ void hw_connect_init(void) { LED_6 = (NRF_GPIO_PIN_MAP(1, 9)); LED_7 = (NRF_GPIO_PIN_MAP(0, 8)); LED_8 = (NRF_GPIO_PIN_MAP(0, 6)); + LED_R = (NRF_GPIO_PIN_MAP(0, 24)); + LED_G = (NRF_GPIO_PIN_MAP(0, 22)); + LED_B = (NRF_GPIO_PIN_MAP(1, 0)); RGB_LIST_NUM = 8; RGB_CTRL_NUM = 3; - LF_ANT_DRIVER = (NRF_GPIO_PIN_MAP(0, 31)); - LF_OA_OUT = (NRF_GPIO_PIN_MAP(0, 29)); LF_MOD = (NRF_GPIO_PIN_MAP(1, 13)); LF_RSSI_PIN = (NRF_GPIO_PIN_MAP(0, 2)); LF_RSSI = NRF_LPCOMP_INPUT_0; + BUTTON_1 = (NRF_GPIO_PIN_MAP(1, 2)); + BUTTON_2 = (NRF_GPIO_PIN_MAP(0, 26)); + + BAT_SENSE_PIN = (NRF_GPIO_PIN_MAP(0, 4)); + BAT_SENSE = NRF_SAADC_INPUT_AIN2; + + // Ultra only + LF_ANT_DRIVER = (NRF_GPIO_PIN_MAP(0, 31)); + LF_OA_OUT = (NRF_GPIO_PIN_MAP(0, 29)); + HF_SPI_SELECT = (NRF_GPIO_PIN_MAP(1, 6)); HF_SPI_MISO = (NRF_GPIO_PIN_MAP(0, 11)); HF_SPI_MOSI = (NRF_GPIO_PIN_MAP(1, 7)); @@ -134,12 +142,6 @@ void hw_connect_init(void) { HF_ANT_SEL = (NRF_GPIO_PIN_MAP(1, 10)); READER_POWER = (NRF_GPIO_PIN_MAP(1, 15)); - - BUTTON_2 = (NRF_GPIO_PIN_MAP(0, 26)); - BUTTON_1 = (NRF_GPIO_PIN_MAP(1, 2)); - - BAT_SENSE_PIN = (NRF_GPIO_PIN_MAP(0, 4)); - BAT_SENSE = NRF_SAADC_INPUT_AIN2; } #endif @@ -236,20 +238,36 @@ void init_leds(void) { * @brief Function for enter tag emulation mode * @param color: 0 means r, 1 means g, 2 means b */ -void set_slot_light_color(uint8_t color) { +void set_slot_light_color(chameleon_rgb_type_t color) { nrf_gpio_pin_set(LED_R); nrf_gpio_pin_set(LED_G); nrf_gpio_pin_set(LED_B); switch (color) { - case 0: + case RGB_RED: + nrf_gpio_pin_clear(LED_R); + break; + case RGB_GREEN: + nrf_gpio_pin_clear(LED_G); + break; + case RGB_BLUE: + nrf_gpio_pin_clear(LED_B); + break; + case RGB_MAGENTA: + nrf_gpio_pin_clear(LED_B); nrf_gpio_pin_clear(LED_R); break; - case 1: + case RGB_YELLOW: + nrf_gpio_pin_clear(LED_R); nrf_gpio_pin_clear(LED_G); break; - case 2: + case RGB_CYAN: + nrf_gpio_pin_clear(LED_G); + nrf_gpio_pin_clear(LED_B); + break; + case RGB_WHITE: + nrf_gpio_pin_clear(LED_R); + nrf_gpio_pin_clear(LED_G); nrf_gpio_pin_clear(LED_B); break; } - } diff --git a/firmware/common/hw_connect.h b/firmware/common/hw_connect.h index 92aa64b5..8e1b6b6f 100644 --- a/firmware/common/hw_connect.h +++ b/firmware/common/hw_connect.h @@ -13,6 +13,16 @@ typedef enum { CHAMELEON_LITE, } chameleon_device_type_t; +typedef enum { + RGB_RED, + RGB_GREEN, + RGB_BLUE, + RGB_MAGENTA, + RGB_YELLOW, + RGB_CYAN, + RGB_WHITE +} chameleon_rgb_type_t; + #define MAX_LED_NUM 8 #define MAX_RGB_NUM 3 @@ -97,7 +107,7 @@ uint32_t *hw_get_led_reversal_array(void); uint32_t *hw_get_rgb_array(void); chameleon_device_type_t hw_get_device_type(void); uint8_t hw_get_version_code(void); -void set_slot_light_color(uint8_t color); +void set_slot_light_color(chameleon_rgb_type_t color); #endif diff --git a/firmware/common/lwip_def.h b/firmware/common/lwip_def.h deleted file mode 100644 index 5559a082..00000000 --- a/firmware/common/lwip_def.h +++ /dev/null @@ -1,142 +0,0 @@ -/** - * @file - * various utility macros - */ - -/* - * Copyright (c) 2001-2004 Swedish Institute of Computer Science. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT - * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT - * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING - * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY - * OF SUCH DAMAGE. - * - * This file is part of the lwIP TCP/IP stack. - * - * Author: Adam Dunkels - * - */ - -/** - * @defgroup perf Performance measurement - * @ingroup sys_layer - * All defines related to this section must not be placed in lwipopts.h, - * but in arch/perf.h! - * Measurement calls made throughout lwip, these can be defined to nothing. - * - PERF_START: start measuring something. - * - PERF_STOP(x): stop measuring something, and record the result. - */ - -#ifndef LWIP_HDR_DEF_H -#define LWIP_HDR_DEF_H - -/* arch.h might define NULL already */ -#define PERF_START /* null definition */ -#define PERF_STOP(x) /* null definition */ - -#ifdef __cplusplus -extern "C" { -#endif - -#define LWIP_MAX(x , y) (((x) > (y)) ? (x) : (y)) -#define LWIP_MIN(x , y) (((x) < (y)) ? (x) : (y)) - -/* Get the number of entries in an array ('x' must NOT be a pointer!) */ -#define LWIP_ARRAYSIZE(x) (sizeof(x)/sizeof((x)[0])) - -/** Create uint32_t value from bytes */ -#define LWIP_MAKEU32(a,b,c,d) (((uint32_t)((a) & 0xff) << 24) | \ - ((uint32_t)((b) & 0xff) << 16) | \ - ((uint32_t)((c) & 0xff) << 8) | \ - (uint32_t)((d) & 0xff)) - -#ifndef NULL -#ifdef __cplusplus -#define NULL 0 -#else -#define NULL ((void *)0) -#endif -#endif - -#if BYTE_ORDER == BIG_ENDIAN -#define lwip_htons(x) ((uint16_t)(x)) -#define lwip_ntohs(x) ((uint16_t)(x)) -#define lwip_htonl(x) ((uint32_t)(x)) -#define lwip_ntohl(x) ((uint32_t)(x)) -#define PP_HTONS(x) ((uint16_t)(x)) -#define PP_NTOHS(x) ((uint16_t)(x)) -#define PP_HTONL(x) ((uint32_t)(x)) -#define PP_NTOHL(x) ((uint32_t)(x)) -#else /* BYTE_ORDER != BIG_ENDIAN */ -#ifndef lwip_htons -uint16_t lwip_htons(uint16_t x); -#endif -#define lwip_ntohs(x) lwip_htons(x) - -#ifndef lwip_htonl -uint32_t lwip_htonl(uint32_t x); -#endif -#define lwip_ntohl(x) lwip_htonl(x) - -/* These macros should be calculated by the preprocessor and are used - with compile-time constants only (so that there is no little-endian - overhead at runtime). */ -#define PP_HTONS(x) ((uint16_t)((((x) & (uint16_t)0x00ffU) << 8) | (((x) & (uint16_t)0xff00U) >> 8))) -#define PP_NTOHS(x) PP_HTONS(x) -#define PP_HTONL(x) ((((x) & (uint32_t)0x000000ffUL) << 24) | \ - (((x) & (uint32_t)0x0000ff00UL) << 8) | \ - (((x) & (uint32_t)0x00ff0000UL) >> 8) | \ - (((x) & (uint32_t)0xff000000UL) >> 24)) -#define PP_NTOHL(x) PP_HTONL(x) -#endif /* BYTE_ORDER == BIG_ENDIAN */ - -/* Functions that are not available as standard implementations. - * In cc.h, you can #define these to implementations available on - * your platform to save some code bytes if you use these functions - * in your application, too. - */ - -#ifndef lwip_itoa -/* This can be #defined to itoa() or snprintf(result, bufsize, "%d", number) depending on your platform */ -void lwip_itoa(char *result, size_t bufsize, int number); -#endif -#ifndef lwip_strnicmp -/* This can be #defined to strnicmp() or strncasecmp() depending on your platform */ -int lwip_strnicmp(const char *str1, const char *str2, size_t len); -#endif -#ifndef lwip_stricmp -/* This can be #defined to stricmp() or strcasecmp() depending on your platform */ -int lwip_stricmp(const char *str1, const char *str2); -#endif -#ifndef lwip_strnstr -/* This can be #defined to strnstr() depending on your platform */ -char *lwip_strnstr(const char *buffer, const char *token, size_t n); -#endif -#ifndef lwip_strnistr -/* This can be #defined to strnistr() depending on your platform */ -char *lwip_strnistr(const char *buffer, const char *token, size_t n); -#endif - -#ifdef __cplusplus -} -#endif - -#endif /* LWIP_HDR_DEF_H */ diff --git a/firmware/common/utils.h b/firmware/common/utils.h index 08e8d5ee..dcd367fc 100644 --- a/firmware/common/utils.h +++ b/firmware/common/utils.h @@ -4,4 +4,19 @@ // u32 size align. #define ALIGN_U32 __attribute__((aligned(4))) +#define PACKED __attribute__((packed)) + +#ifndef ARRAYLEN +# define ARRAYLEN(x) (sizeof(x)/sizeof((x)[0])) +#endif + +// nrf52840 platform is little endian +#define U16HTONS(x) ((uint16_t)((((x) & (uint16_t)0x00ffU) << 8) | (((x) & (uint16_t)0xff00U) >> 8))) +#define U16NTOHS(x) U16HTONS(x) +#define U32HTONL(x) ((((x) & (uint32_t)0x000000ffUL) << 24) | \ + (((x) & (uint32_t)0x0000ff00UL) << 8) | \ + (((x) & (uint32_t)0x00ff0000UL) >> 8) | \ + (((x) & (uint32_t)0xff000000UL) >> 24)) +#define U32NTOHL(x) U32HTONL(x) + #endif diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index 440e22f9..5d5aad5c 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -8,21 +8,43 @@ import sys import time import serial.tools.list_ports +import threading +import struct from platform import uname import chameleon_com import chameleon_cmd -import chameleon_cstruct import chameleon_status from chameleon_utils import ArgumentParserNoExit, ArgsParserError, UnexpectedResponseError from chameleon_utils import CLITree +# Colorama shorthands +CR = colorama.Fore.RED +CG = colorama.Fore.GREEN +CC = colorama.Fore.CYAN +CY = colorama.Fore.YELLOW +C0 = colorama.Style.RESET_ALL + +# NXP IDs based on https://www.nxp.com/docs/en/application-note/AN10833.pdf +type_id_SAK_dict = {0x00: "MIFARE Ultralight Classic/C/EV1/Nano | NTAG 2xx", + 0x08: "MIFARE Classic 1K | Plus SE 1K | Plug S 2K | Plus X 2K", + 0x09: "MIFARE Mini 0.3k", + 0x10: "MIFARE Plus 2K", + 0x11: "MIFARE Plus 4K", + 0x18: "MIFARE Classic 4K | Plus S 4K | Plus X 4K", + 0x19: "MIFARE Classic 2K", + 0x20: "MIFARE Plus EV1/EV2 | DESFire EV1/EV2/EV3 | DESFire Light | NTAG 4xx | MIFARE Plus S 2/4K | MIFARE Plus X 2/4K | MIFARE Plus SE 1K", + 0x28: "SmartMX with MIFARE Classic 1K", + 0x38: "SmartMX with MIFARE Classic 4K", + } + class BaseCLIUnit: def __init__(self): # new a device command transfer and receiver instance(Send cmd and receive response) self._device_com: chameleon_com.ChameleonCom | None = None + self._device_cmd: chameleon_cmd.ChameleonCMD = chameleon_cmd.ChameleonCMD(self._device_com) @property def device_com(self) -> chameleon_com.ChameleonCom: @@ -31,10 +53,11 @@ def device_com(self) -> chameleon_com.ChameleonCom: @device_com.setter def device_com(self, com): self._device_com = com + self._device_cmd = chameleon_cmd.ChameleonCMD(self._device_com) @property def cmd(self) -> chameleon_cmd.ChameleonCMD: - return chameleon_cmd.ChameleonCMD(self.device_com) + return self._device_cmd def args_parser(self) -> ArgumentParserNoExit or None: """ @@ -61,9 +84,17 @@ def on_exec(self, args: argparse.Namespace): def sub_process(cmd, cwd=os.path.abspath("bin/")): class ShadowProcess: def __init__(self): + self.output = "" self.time_start = timeit.default_timer() self._process = subprocess.Popen(cmd, cwd=cwd, shell=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE) + threading.Thread(target=self.thread_read_output).start() + + def thread_read_output(self): + while self._process.poll() is None: + data = self._process.stdout.read(1024) + if len(data) > 0: + self.output += data.decode(encoding="utf-8") def get_time_distance(self, ms=True): if ms: @@ -80,15 +111,8 @@ def is_timeout(self, timeout_ms): return True return False - def get_output_sync(self, encoding='utf-8'): - buffer = bytearray() - while True: - data = self._process.stdout.read(1024) - if len(data) > 0: - buffer.extend(data) - else: - break - return buffer.decode(encoding) + def get_output_sync(self): + return self.output def get_ret_code(self): return self._process.poll() @@ -139,7 +163,7 @@ def args_parser(self) -> ArgumentParserNoExit or None: def before_exec(self, args: argparse.Namespace): if super(ReaderRequiredUnit, self).before_exec(args): - ret = self.cmd.is_reader_device_mode() + ret = self.cmd.is_device_reader_mode() if ret: return True else: @@ -167,6 +191,7 @@ def on_exec(self, args: argparse.Namespace): hf_14a = hf.subgroup('14a', 'ISO14443-a tag read/write/info...') hf_mf = hf.subgroup('mf', 'Mifare Classic mini/1/2/4, attack/read/write') hf_mf_detection = hf.subgroup('detection', 'Mifare Classic detection log') +hf_mfu = hf.subgroup('mfu', 'Mifare Ultralight, read/write') lf = CLITree('lf', 'low frequency tag/reader') lf_em = lf.subgroup('em', 'EM410x read/write/emulator') @@ -219,9 +244,14 @@ def on_exec(self, args: argparse.Namespace): print("Chameleon not found, please connect the device or try connecting manually with the -p flag.") return self.device_com.open(args.port) - print(" { Chameleon connected } ") + self.device_com.commands = self.cmd.get_device_capabilities() + major, minor = self.cmd.get_app_version() + model = ['Ultra', 'Lite'][self.cmd.get_device_model()] + print(f" {{ Chameleon {model} connected: v{major}.{minor} }}") + except Exception as e: - print(f"Chameleon Connect fail: {str(e)}") + print(f"{CR}Chameleon Connect fail: {str(e)}{C0}") + self.device_com.close() @hw_mode.command('set', 'Change device mode to tag reader or tag emulator') @@ -235,10 +265,10 @@ def args_parser(self) -> ArgumentParserNoExit or None: def on_exec(self, args: argparse.Namespace): if args.mode == 'reader' or args.mode == 'r': - self.cmd.set_reader_device_mode(True) + self.cmd.set_device_reader_mode(True) print("Switch to { Tag Reader } mode successfully.") else: - self.cmd.set_reader_device_mode(False) + self.cmd.set_device_reader_mode(False) print("Switch to { Tag Emulator } mode successfully.") @@ -248,7 +278,7 @@ def args_parser(self) -> ArgumentParserNoExit or None: pass def on_exec(self, args: argparse.Namespace): - print(f"- Device Mode ( Tag {'Reader' if self.cmd.is_reader_device_mode() else 'Emulator'} )") + print(f"- Device Mode ( Tag {'Reader' if self.cmd.is_device_reader_mode() else 'Emulator'} )") @hw_chipid.command('get', 'Get device chipset ID') @@ -275,10 +305,11 @@ def args_parser(self) -> ArgumentParserNoExit or None: return None def on_exec(self, args: argparse.Namespace): - fw_version_int = self.cmd.get_firmware_version() - fw_version = f'v{fw_version_int // 256}.{fw_version_int % 256}' + fw_version_tuple = self.cmd.get_app_version() + fw_version = f'v{fw_version_tuple[0]}.{fw_version_tuple[1]}' git_version = self.cmd.get_git_version() - print(f' - Version: {fw_version} ({git_version})') + model = ['Ultra', 'Lite'][self.cmd.get_device_model()] + print(f' - Chameleon {model}, Version: {fw_version} ({git_version})') @hf_14a.command('scan', 'Scan 14a tag, and print basic information') @@ -286,21 +317,42 @@ class HF14AScan(ReaderRequiredUnit): def args_parser(self) -> ArgumentParserNoExit or None: pass - def scan(self): - resp: chameleon_com.Response = self.cmd.scan_tag_14a() - if resp.status == chameleon_status.Device.HF_TAG_OK: - info = chameleon_cstruct.parse_14a_scan_tag_result(resp.data) - print(f"- UID Size: {info['uid_size']}") - print(f"- UID Hex : {info['uid_hex'].upper()}") - print(f"- SAK Hex : {info['sak_hex'].upper()}") - print(f"- ATQA Hex : {info['atqa_hex'].upper()}") - return True + def check_mf1_nt(self): + # detect mf1 support + if self.cmd.mf1_detect_support(): + # detect prng + print("- Mifare Classic technology") + prng_type = self.cmd.mf1_detect_prng() + print(f" # Prng: {chameleon_cmd.MifareClassicPrngType(prng_type)}") + + def sak_info(self, data_tag): + # detect the technology in use based on SAK + int_sak = data_tag['sak'][0] + if int_sak in type_id_SAK_dict: + print(f"- Guessed type(s) from SAK: {type_id_SAK_dict[int_sak]}") + + def scan(self, deep=False): + resp = self.cmd.hf14a_scan() + if resp is not None: + for data_tag in resp: + print(f"- UID : {data_tag['uid'].hex().upper()}") + print(f"- ATQA : {data_tag['atqa'].hex().upper()}") + print(f"- SAK : {data_tag['sak'].hex().upper()}") + if len(data_tag['ats']) > 0: + print(f"- ATS : {data_tag['ats'].hex().upper()}") + if deep: + self.sak_info(data_tag) + # TODO: following checks cannot be done yet if multiple cards are present + if len(resp) == 1: + self.check_mf1_nt() + # TODO: check for ATS support on 14A3 tags + else: + print("Multiple tags detected, skipping deep tests...") else: print("ISO14443-A Tag no found") - return False def on_exec(self, args: argparse.Namespace): - return self.scan() + self.scan() @hf_14a.command('info', 'Scan 14a tag, and print detail information') @@ -308,29 +360,10 @@ class HF14AInfo(ReaderRequiredUnit): def args_parser(self) -> ArgumentParserNoExit or None: pass - def info(self): - # detect mf1 support - resp = self.cmd.detect_mf1_support() - if resp.status == chameleon_status.Device.HF_TAG_OK: - # detect prng - print("- Mifare Classic technology") - resp = self.cmd.detect_mf1_nt_level() - if resp.status == 0x00: - prng_level = "Weak" - elif resp.status == 0x24: - prng_level = "Static" - elif resp.status == 0x25: - prng_level = "Hard" - else: - prng_level = "Unknown" - print(f" # Prng attack: {prng_level}") - def on_exec(self, args: argparse.Namespace): - # reused scan = HF14AScan() scan.device_com = self.device_com - if scan.scan(): - self.info() + scan.scan(deep=1) @hf_mf.command('nested', 'Mifare Classic nested recover key') @@ -352,6 +385,14 @@ def args_parser(self) -> ArgumentParserNoExit or None: # hf mf nested -o --block-known 0 --type-known A --key FFFFFFFFFFFF --block-target 4 --type-target A return parser + def from_nt_level_code_to_str(self, nt_level): + if nt_level == 0: + return 'StaticNested' + if nt_level == 1: + return 'Nested' + if nt_level == 2: + return 'HardNested' + def recover_a_key(self, block_known, type_known, key_known, block_target, type_target) -> str or None: """ recover a key from key known @@ -362,27 +403,45 @@ def recover_a_key(self, block_known, type_known, key_known, block_target, type_t :param type_target: :return: """ + # check nt level, we can run static or nested auto... + nt_level = self.cmd.mf1_detect_prng() + print(f" - NT vulnerable: {CY}{ self.from_nt_level_code_to_str(nt_level) }{C0}") + if nt_level == 2: + print(" [!] HardNested has not been implemented yet.") + return None + # acquire - dist_resp = self.cmd.detect_nt_distance(block_known, type_known, key_known) - nt_resp = self.cmd.acquire_nested(block_known, type_known, key_known, block_target, type_target) - # parse - dist_obj = chameleon_cstruct.parse_nt_distance_detect_result(dist_resp.data) - nt_obj = chameleon_cstruct.parse_nested_nt_acquire_group(nt_resp.data) - # create cmd - cmd_param = f"{dist_obj['uid']} {dist_obj['dist']}" - for nt_item in nt_obj: - cmd_param += f" {nt_item['nt']} {nt_item['nt_enc']} {nt_item['par']}" + if nt_level == 0: # It's a staticnested tag? + nt_uid_obj = self.cmd.mf1_static_nested_acquire( + block_known, type_known, key_known, block_target, type_target) + cmd_param = f"{nt_uid_obj['uid']} {str(type_target)}" + for nt_item in nt_uid_obj['nts']: + cmd_param += f" {nt_item['nt']} {nt_item['nt_enc']}" + decryptor_name = "staticnested" + else: + dist_obj = self.cmd.mf1_detect_nt_dist(block_known, type_known, key_known) + nt_obj = self.cmd.mf1_nested_acquire(block_known, type_known, key_known, block_target, type_target) + # create cmd + cmd_param = f"{dist_obj['uid']} {dist_obj['dist']}" + for nt_item in nt_obj: + cmd_param += f" {nt_item['nt']} {nt_item['nt_enc']} {nt_item['par']}" + decryptor_name = "nested" + + # Cross-platform compatibility if sys.platform == "win32": - cmd_recover = f"nested.exe {cmd_param}" + cmd_recover = f"{decryptor_name}.exe {cmd_param}" else: - cmd_recover = f"./nested {cmd_param}" + cmd_recover = f"./{decryptor_name} {cmd_param}" + + print(f" Executing {cmd_recover}") # start a decrypt process process = self.sub_process(cmd_recover) # wait end while process.is_running(): - msg = f" [ Time elapsed {process.get_time_distance()}ms ]\r" + msg = f" [ Time elapsed {process.get_time_distance()/1000:#.1f}s ]\r" print(msg, end="") + time.sleep(0.1) # clear \r print() @@ -395,11 +454,10 @@ def recover_a_key(self, block_known, type_known, key_known, block_target, type_t key_list.append(sea_obj[1]) # Here you have to verify the password first, and then get the one that is successfully verified # If there is no verified password, it means that the recovery failed, you can try again - print(f" - [{len(key_list)} candidate keys found ]") + print(f" - [{len(key_list)} candidate key(s) found ]") for key in key_list: key_bytes = bytearray.fromhex(key) - ret = self.cmd.auth_mf1_key(block_target, type_target, key_bytes) - if ret.status == chameleon_status.Device.HF_TAG_OK: + if self.cmd.mf1_auth_one_key_block(block_target, type_target, key_bytes): return key else: # No keys recover, and no errors. @@ -422,7 +480,7 @@ def on_exec(self, args: argparse.Namespace): type_target = args.type_target if block_target is not None and type_target is not None: type_target = 0x60 if type_target == 'A' or type_target == 'a' else 0x61 - print(f" - {colorama.Fore.CYAN}Nested recover one key running...{colorama.Style.RESET_ALL}") + print(f" - {C0}Nested recover one key running...{C0}") key = self.recover_a_key(block_known, type_known, key_known, block_target, type_target) if key is None: print("No keys found, you can retry recover.") @@ -456,9 +514,16 @@ def recover_key(self, block_target, type_target): first_recover = True retry_count = 0 while retry_count < 0xFF: - darkside_resp = self.cmd.acquire_darkside(block_target, type_target, first_recover, 15) + darkside_resp = self.cmd.mf1_darkside_acquire(block_target, type_target, first_recover, 30) first_recover = False # not first run. - darkside_obj = chameleon_cstruct.parse_darkside_acquire_result(darkside_resp.data) + if darkside_resp[0] != chameleon_cmd.MifareClassicDarksideStatus.OK: + print(f"Darkside error: {chameleon_cmd.MifareClassicDarksideStatus(darkside_resp[0])}") + break + darkside_obj = darkside_resp[1] + + if darkside_obj['par'] != 0: # NXP tag workaround. + self.darkside_list.clear() + self.darkside_list.append(darkside_obj) recover_params = f"{darkside_obj['uid']}" for darkside_item in self.darkside_list: @@ -469,7 +534,7 @@ def recover_key(self, block_target, type_target): else: cmd_recover = f"./darkside {recover_params}" # subprocess.run(cmd_recover, cwd=os.path.abspath("../bin/"), shell=True) - # print(cmd_recover) + # print(f" Executing {cmd_recover}") # start a decrypt process process = self.sub_process(cmd_recover) # wait end @@ -489,8 +554,7 @@ def recover_key(self, block_target, type_target): # auth key for key in key_list: key_bytes = bytearray.fromhex(key) - auth_ret = self.cmd.auth_mf1_key(block_target, type_target, key_bytes) - if auth_ret.status == chameleon_status.Device.HF_TAG_OK: + if self.cmd.mf1_auth_one_key_block(block_target, type_target, key_bytes): return key return None @@ -530,13 +594,31 @@ def on_exec(self, args: argparse.Namespace): raise NotImplementedError("Please implement this") +class BaseMFUAuthOpera(ReaderRequiredUnit): + def args_parser(self) -> ArgumentParserNoExit or None: + parser = ArgumentParserNoExit() + # TODO: + # -k, --key Authentication key (UL-C 16 bytes, EV1/NTAG 4 bytes) + # -l Swap entered key's endianness + return parser + + def get_param(self, args): + class Param: + def __init__(self): + pass + return Param() + + def on_exec(self, args: argparse.Namespace): + raise NotImplementedError("Please implement this") + + @hf_mf.command('rdbl', 'Mifare Classic read one block') class HFMFRDBL(BaseMF1AuthOpera): # hf mf rdbl -b 2 -t A -k FFFFFFFFFFFF def on_exec(self, args: argparse.Namespace): param = self.get_param(args) - resp = self.cmd.read_mf1_block(param.block, param.type, param.key) - print(f" - Data: {resp.data.hex()}") + resp = self.cmd.mf1_read_one_block(param.block, param.type, param.key) + print(f" - Data: {resp.hex()}") @hf_mf.command('wrbl', 'Mifare Classic write one block') @@ -553,11 +635,11 @@ def on_exec(self, args: argparse.Namespace): if not re.match(r"^[a-fA-F0-9]{32}$", args.data): raise ArgsParserError("Data must include 32 HEX symbols") param.data = bytearray.fromhex(args.data) - resp = self.cmd.write_mf1_block(param.block, param.type, param.key, param.data) - if resp.status == chameleon_status.Device.HF_TAG_OK: - print(f" - {colorama.Fore.GREEN}Write done.{colorama.Style.RESET_ALL}") + resp = self.cmd.mf1_write_one_block(param.block, param.type, param.key, param.data) + if resp: + print(f" - {CG}Write done.{C0}") else: - print(f" - {colorama.Fore.RED}Write fail.{colorama.Style.RESET_ALL}") + print(f" - {CR}Write fail.{C0}") @hf_mf_detection.command('enable', 'Detection enable') @@ -570,7 +652,7 @@ def args_parser(self) -> ArgumentParserNoExit or None: # hf mf detection enable -e 1 def on_exec(self, args: argparse.Namespace): enable = True if args.enable == 1 else False - self.cmd.set_mf1_detection_enable(enable) + self.cmd.mf1_set_detection_enable(enable) print(f" - Set mf1 detection {'enable' if enable else 'disable'}.") @@ -581,8 +663,7 @@ def args_parser(self) -> ArgumentParserNoExit or None: # hf mf detection count def on_exec(self, args: argparse.Namespace): - data_bytes = self.cmd.get_mf1_detection_count().data - count = int.from_bytes(data_bytes, "little", signed=False) + count = self.cmd.mf1_get_detection_count() print(f" - MF1 detection log count = {count}") @@ -599,11 +680,17 @@ def decrypt_by_list(self, rs: list): :param rs: :return: """ - keys = [] + msg1 = f" > {len(rs)} records => " + msg2 = f"/{(len(rs)*(len(rs)-1))//2} combinations. " + msg3 = f" key(s) found" + n = 1 + keys = set() for i in range(len(rs)): item0 = rs[i] for j in range(i + 1, len(rs)): item1 = rs[j] + # TODO: if some keys already recovered, test them on item before running mfkey32 on item + # TODO: if some keys already recovered, remove corresponding items cmd_base = f"{item0['uid']} {item0['nt']} {item0['nr']} {item0['ar']}" cmd_base += f" {item1['nt']} {item1['nr']} {item1['ar']}" if sys.platform == "win32": @@ -621,29 +708,44 @@ def decrypt_by_list(self, rs: list): # print(output_str) sea_obj = re.search(r"([a-fA-F0-9]{12})", output_str, flags=re.MULTILINE) if sea_obj is not None: - keys.append(sea_obj[1]) - + keys.add(sea_obj[1]) + print(f"{msg1}{n}{msg2}{len(keys)}{msg3}\r", end="") + n += 1 + print() return keys # hf mf detection decrypt def on_exec(self, args: argparse.Namespace): - buffer = bytearray() index = 0 - count = int.from_bytes(self.cmd.get_mf1_detection_count().data, "little", signed=False) + count = self.cmd.mf1_get_detection_count() if count == 0: print(" - No detection log to download") return print(f" - MF1 detection log count = {count}, start download", end="") + result_list = [] while index < count: - tmp = self.cmd.get_mf1_detection_log(index).data - recv_count = int(len(tmp) / HFMFDetectionDecrypt.detection_log_size) + tmp = self.cmd.mf1_get_detection_log(index) + recv_count = len(tmp) index += recv_count - buffer.extend(tmp) - print(".", end="") + result_list.extend(tmp) + print("."*recv_count, end="") print() - print(f" - Download done ({len(buffer)}bytes), start parse and decrypt") + print(f" - Download done ({len(result_list)} records), start parse and decrypt") + # classify + result_maps = {} + for item in result_list: + uid = item['uid'] + if uid not in result_maps: + result_maps[uid] = {} + block = item['block'] + if block not in result_maps[uid]: + result_maps[uid][block] = {} + type = item['type'] + if type not in result_maps[uid][block]: + result_maps[uid][block][type] = [] + + result_maps[uid][block][type].append(item) - result_maps = chameleon_cstruct.parse_mf1_detection_result(buffer) for uid in result_maps.keys(): print(f" - Detection log for uid [{uid.upper()}]") result_maps_for_uid = result_maps[uid] @@ -654,11 +756,15 @@ def on_exec(self, args: argparse.Namespace): records = result_maps_for_uid[block]['A'] if len(records) > 1: result_maps[uid][block]['A'] = self.decrypt_by_list(records) + else: + print(f" > {len(records)} record") if 'B' in result_maps_for_uid[block]: # print(f" - B record: { result_maps[block]['B'] }") records = result_maps_for_uid[block]['B'] if len(records) > 1: result_maps[uid][block]['B'] = self.decrypt_by_list(records) + else: + print(f" > {len(records)} record") print(" > Result ---------------------------") for block in result_maps_for_uid.keys(): if 'A' in result_maps_for_uid[block]: @@ -704,14 +810,16 @@ def on_exec(self, args: argparse.Namespace): index = 0 block = 0 - while index < len(buffer): + max_blocks = (self.device_com.data_max_length - 1) // 16 + while index + 16 < len(buffer): # split a block from buffer - block_data = buffer[index: index + 16] - index += 16 + block_data = buffer[index: index + 16*max_blocks] + n_blocks = len(block_data) // 16 + index += 16*n_blocks # load to device - self.cmd.set_mf1_block_data(block, block_data) - print('.', end='') - block += 1 + self.cmd.mf1_write_emu_block_data(block, block_data) + print('.'*n_blocks, end='') + block += n_blocks print("\n - Load success") @@ -735,9 +843,9 @@ def on_exec(self, args: argparse.Namespace): else: content_type = args.type - selected_slot = self.cmd.get_active_slot().data[0] - slot_info = self.cmd.get_slot_info().data - tag_type = chameleon_cmd.TagSpecificType(slot_info[selected_slot * 2]) + selected_slot = self.cmd.get_active_slot() + slot_info = self.cmd.get_slot_info() + tag_type = chameleon_cmd.TagSpecificType(slot_info[selected_slot]['hf']) if tag_type == chameleon_cmd.TagSpecificType.TAG_TYPE_MIFARE_Mini: block_count = 20 elif tag_type == chameleon_cmd.TagSpecificType.TAG_TYPE_MIFARE_1024: @@ -749,19 +857,22 @@ def on_exec(self, args: argparse.Namespace): else: raise Exception("Card in current slot is not Mifare Classic/Plus in SL1 mode") - with open(file, 'wb') as fd: - block = 0 - while block < block_count: - response = self.cmd.get_mf1_block_data(block, 1) - print('.', end='') - block += 1 - if content_type == 'hex': - hex_char_repr = binascii.hexlify(response.data) - fd.write(hex_char_repr) - fd.write(bytes([0x0a])) - else: - fd.write(response.data) + index = 0 + data = bytearray(0) + max_blocks = self.device_com.data_max_length // 16 + while block_count > 0: + chunk_count = min(block_count, max_blocks) + data.extend(self.cmd.mf1_read_emu_block_data(index, chunk_count)) + index += chunk_count + block_count -= chunk_count + print('.'*chunk_count, end='') + with open(file, 'wb') as fd: + if content_type == 'hex': + for i in range(len(data) // 16): + fd.write(binascii.hexlify(data[i*16:(i+1)*16])+b'\n') + else: + fd.write(data) print("\n - Read success") @@ -789,16 +900,16 @@ def args_parser(self) -> ArgumentParserNoExit or None: # hf mf settings def on_exec(self, args: argparse.Namespace): if args.gen1a != -1: - self.cmd.set_mf1_gen1a_mode(args.gen1a) + self.cmd.mf1_set_gen1a_mode(args.gen1a) print(f' - Set gen1a mode to {"enabled" if args.gen1a else "disabled"} success') if args.gen2 != -1: - self.cmd.set_mf1_gen2_mode(args.gen2) + self.cmd.mf1_set_gen2_mode(args.gen2) print(f' - Set gen2 mode to {"enabled" if args.gen2 else "disabled"} success') if args.coll != -1: - self.cmd.set_mf1_block_anti_coll_mode(args.coll) + self.cmd.mf1_set_block_anti_coll_mode(args.coll) print(f' - Set anti-collision mode to {"enabled" if args.coll else "disabled"} success') if args.write != -1: - self.cmd.set_mf1_write_mode(args.write) + self.cmd.mf1_set_write_mode(args.write) print(f' - Set write mode to {chameleon_cmd.MifareClassicWriteMode(args.write)} success') print(' - Emulator settings updated') @@ -807,36 +918,44 @@ def on_exec(self, args: argparse.Namespace): class HFMFSim(DeviceRequiredUnit): def args_parser(self) -> ArgumentParserNoExit or None: parser = ArgumentParserNoExit() - parser.add_argument('--sak', type=str, required=True, help="Select AcKnowledge(hex)", metavar="hex") - parser.add_argument('--atqa', type=str, required=True, help="Answer To Request(hex)", metavar="hex") parser.add_argument('--uid', type=str, required=True, help="Unique ID(hex)", metavar="hex") + parser.add_argument('--atqa', type=str, required=True, help="Answer To Request(hex)", metavar="hex") + parser.add_argument('--sak', type=str, required=True, help="Select AcKnowledge(hex)", metavar="hex") + parser.add_argument('--ats', type=str, required=False, help="Answer To Select(hex)", metavar="hex") return parser # hf mf sim --sak 08 --atqa 0400 --uid DEADBEEF def on_exec(self, args: argparse.Namespace): - sak_str: str = args.sak.strip() - atqa_str: str = args.atqa.strip() uid_str: str = args.uid.strip() - - if re.match(r"[a-fA-F0-9]{2}", sak_str) is not None: - sak = bytearray.fromhex(sak_str) + if re.match(r"[a-fA-F0-9]+", uid_str) is not None: + uid = bytes.fromhex(uid_str) + if len(uid) not in [4, 7, 10]: + raise Exception("UID length error") else: - raise Exception("SAK must be hex(2byte)") + raise Exception("UID must be hex") + atqa_str: str = args.atqa.strip() if re.match(r"[a-fA-F0-9]{4}", atqa_str) is not None: - atqa = bytearray.fromhex(atqa_str) + atqa = bytes.fromhex(atqa_str) else: raise Exception("ATQA must be hex(4byte)") - if re.match(r"[a-fA-F0-9]+", uid_str) is not None: - uid_len = len(uid_str) - if uid_len != 8 and uid_len != 14 and uid_len != 20: - raise Exception("UID length error") - uid = bytearray.fromhex(uid_str) + sak_str: str = args.sak.strip() + if re.match(r"[a-fA-F0-9]{2}", sak_str) is not None: + sak = bytes.fromhex(sak_str) else: - raise Exception("UID must be hex") + raise Exception("SAK must be hex(2byte)") + + if args.ats is not None: + ats_str: str = args.ats.strip() + if re.match(r"[a-fA-F0-9]+", ats_str) is not None: + ats = bytes.fromhex(ats_str) + else: + raise Exception("ATS must be hex") + else: + ats = b'' - self.cmd.set_mf1_anti_collision_res(sak, atqa, uid) + self.cmd.hf14a_set_anti_coll_data(uid, atqa, sak, ats) print(" - Set anti-collision resources success") @@ -846,31 +965,112 @@ def args_parser(self) -> ArgumentParserNoExit or None: pass def scan(self): - resp: chameleon_com.Response = self.cmd.get_mf1_anti_coll_data() - if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: - info = chameleon_cstruct.parse_14a_scan_tag_result(resp.data) - print(f"- UID Size: {info['uid_size']}") - print(f"- UID Hex : {info['uid_hex'].upper()}") - print(f"- SAK Hex : {info['sak_hex'].upper()}") - print(f"- ATQA Hex : {info['atqa_hex'].upper()}") - return True - else: - print("No data loaded in slot") - return False + resp = self.cmd.hf14a_get_anti_coll_data() + print(f"- UID : {resp['uid'].hex().upper()}") + print(f"- ATQA : {resp['atqa'].hex().upper()}") + print(f"- SAK : {resp['sak'].hex().upper()}") + if len(resp['ats']) > 0: + print(f"- ATS : {resp['ats'].hex().upper()}") def on_exec(self, args: argparse.Namespace): return self.scan() +@hf_mfu.command('rdpg', 'MIFARE Ultralight read one page') +class HFMFURDPG(BaseMFUAuthOpera): + # hf mfu rdpg -p 2 + + def args_parser(self) -> ArgumentParserNoExit or None: + parser = super(HFMFURDPG, self).args_parser() + parser.add_argument('-p', '--page', type=int, required=True, metavar="decimal", + help="The page where the key will be used against") + return parser + + def get_param(self, args): + class Param: + def __init__(self): + self.page = args.page + return Param() + + def on_exec(self, args: argparse.Namespace): + param = self.get_param(args) + + options = { + 'activate_rf_field': 0, + 'wait_response': 1, + 'append_crc': 1, + 'auto_select': 1, + 'keep_rf_field': 0, + 'check_response_crc': 1, + } + # TODO: auth first if a key is given + resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!BB', 0x30, param.page)) + print(f" - Data: {resp[:4].hex()}") + + +@hf_mfu.command('dump', 'MIFARE Ultralight dump pages') +class HFMFUDUMP(BaseMFUAuthOpera): + # hf mfu dump [-p start_page] [-q number_pages] [-f output_file] + def args_parser(self) -> ArgumentParserNoExit or None: + parser = super(HFMFUDUMP, self).args_parser() + parser.add_argument('-p', '--page', type=int, required=False, metavar="decimal", default=0, + help="Manually set number of pages to dump") + parser.add_argument('-q', '--qty', type=int, required=False, metavar="decimal", default=16, + help="Manually set number of pages to dump") + parser.add_argument('-f', '--file', type=str, required=False, default="", + help="Specify a filename for dump file") + return parser + + def get_param(self, args): + class Param: + def __init__(self): + self.start_page = args.page + self.stop_page = args.page + args.qty + self.output_file = args.file + return Param() + + def on_exec(self, args: argparse.Namespace): + param = self.get_param(args) + fd = None + save_as_eml = False + if param.output_file != "": + if param.output_file.endswith('.eml'): + fd = open(param.output_file, 'w+') + save_as_eml = True + else: + fd = open(param.output_file, 'wb+') + # TODO: auth first if a key is given + options = { + 'activate_rf_field': 0, + 'wait_response': 1, + 'append_crc': 1, + 'auto_select': 1, + 'keep_rf_field': 0, + 'check_response_crc': 1, + } + for i in range(param.start_page, param.stop_page): + resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!BB', 0x30, i)) + # TODO: can be optimized as we get 4 pages at once but beware of wrapping in case of end of memory or LOCK on ULC and no key provided + data = resp[:4] + print(f" - Page {i:2}: {data.hex()}") + if fd is not None: + if save_as_eml: + fd.write(data.hex()+'\n') + else: + fd.write(data) + if fd is not None: + print(f" - {colorama.Fore.GREEN}Dump written in {param.output_file}.{colorama.Style.RESET_ALL}") + fd.close() + + @lf_em.command('read', 'Scan em410x tag and print id') class LFEMRead(ReaderRequiredUnit): def args_parser(self) -> ArgumentParserNoExit or None: return None def on_exec(self, args: argparse.Namespace): - resp = self.cmd.read_em_410x() - id_hex = resp.data.hex() - print(f" - EM410x ID(10H): {colorama.Fore.GREEN}{id_hex}{colorama.Style.RESET_ALL}") + id = self.cmd.em410x_scan() + print(f" - EM410x ID(10H): {CG}{id.hex()}{C0}") class LFEMCardRequiredUnit(DeviceRequiredUnit): @@ -907,8 +1107,8 @@ def before_exec(self, args: argparse.Namespace): # lf em write --id 4400999559 def on_exec(self, args: argparse.Namespace): id_hex = args.id - id_bytes = bytearray.fromhex(id_hex) - self.cmd.write_em_410x_to_t55xx(id_bytes) + id_bytes = bytes.fromhex(id_hex) + self.cmd.em410x_write_to_t55xx(id_bytes) print(f" - EM410x ID(10H): {id_hex} write done.") @@ -956,40 +1156,73 @@ class HWSlotList(DeviceRequiredUnit): def args_parser(self) -> ArgumentParserNoExit or None: parser = ArgumentParserNoExit() parser.add_argument('-e', '--extend', type=int, required=False, - help="Show slot nicknames and Mifare Classic emulator settings. 0 - skip, 1 - show (" - "default, 2 - show emulator settings for each slot)", choices=[0, 1, 2], default=1) + help="Show slot nicknames and Mifare Classic emulator settings. 0 - skip, 1 - show (default)", choices=[0, 1], default=1) return parser def get_slot_name(self, slot, sense): try: - return self.cmd.get_slot_tag_nick_name(slot, sense).data.decode() + name = self.cmd.get_slot_tag_nick(slot, sense).decode(encoding="utf8") + return {'baselen': len(name), 'metalen': len(CC+C0), 'name': f'{CC}{name}{C0}'} except UnexpectedResponseError: - return "Empty" + return {'baselen': 0, 'metalen': 0, 'name': f''} except UnicodeDecodeError: - return "Non UTF-8" + name = "UTF8 Err" + return {'baselen': len(name), 'metalen': len(CC+C0), 'name': f'{CC}{name}{C0}'} # hw slot list def on_exec(self, args: argparse.Namespace): - data = self.cmd.get_slot_info().data - selected = chameleon_cmd.SlotNumber.from_fw(self.cmd.get_active_slot().data[0]) - enabled = self.cmd.get_enabled_slots().data + slotinfo = self.cmd.get_slot_info() + selected = chameleon_cmd.SlotNumber.from_fw(self.cmd.get_active_slot()) + enabled = self.cmd.get_enabled_slots() + maxnamelength = 0 + if args.extend: + slotnames = [] + for slot in chameleon_cmd.SlotNumber: + hfn = self.get_slot_name(slot, chameleon_cmd.TagSenseType.TAG_SENSE_HF) + lfn = self.get_slot_name(slot, chameleon_cmd.TagSenseType.TAG_SENSE_LF) + m = max(hfn['baselen'], lfn['baselen']) + maxnamelength = m if m > maxnamelength else maxnamelength + slotnames.append({'hf': hfn, 'lf': lfn}) for slot in chameleon_cmd.SlotNumber: - print(f' - Slot {slot} data{" (active)" if slot == selected else ""}' - f'{" (disabled)" if not enabled[chameleon_cmd.SlotNumber.to_fw(slot)] else ""}:') + fwslot = chameleon_cmd.SlotNumber.to_fw(slot) + hf_tag_type = chameleon_cmd.TagSpecificType(slotinfo[fwslot]['hf']) + lf_tag_type = chameleon_cmd.TagSpecificType(slotinfo[fwslot]['lf']) + print(f' - {f"Slot {slot}:":{4+maxnamelength+1}}' + f'{f"({CG}active{C0})" if slot == selected else ""}') print(f' HF: ' - f'{(self.get_slot_name(slot, chameleon_cmd.TagSenseType.TAG_SENSE_HF) + " - ") if args.extend else ""}' - f'{chameleon_cmd.TagSpecificType(data[chameleon_cmd.SlotNumber.to_fw(slot) * 2])}') + f'{(slotnames[fwslot]["hf"]["name"] if args.extend else ""):{maxnamelength+slotnames[fwslot]["hf"]["metalen"]+1 if args.extend else maxnamelength+1}}', end='') + print(f'{f"({CR}disabled{C0}) " if not enabled[fwslot]["hf"] else ""}', end='') + if hf_tag_type != chameleon_cmd.TagSpecificType.TAG_TYPE_UNDEFINED: + print(f"{CY if enabled[fwslot]['hf'] else C0}{hf_tag_type}{C0}") + else: + print("undef") + if args.extend == 1 and \ + enabled[fwslot]['hf'] and \ + slot == selected and \ + hf_tag_type in [ + chameleon_cmd.TagSpecificType.TAG_TYPE_MIFARE_Mini, + chameleon_cmd.TagSpecificType.TAG_TYPE_MIFARE_1024, + chameleon_cmd.TagSpecificType.TAG_TYPE_MIFARE_2048, + chameleon_cmd.TagSpecificType.TAG_TYPE_MIFARE_4096, + ]: + config = self.cmd.mf1_get_emulator_config() + print(' - Mifare Classic emulator settings:') + print( + f' {"Detection (mfkey32) mode:":40}{f"{CG}enabled{C0}" if config["detection"] else f"{CR}disabled{C0}"}') + print( + f' {"Gen1A magic mode:":40}{f"{CG}enabled{C0}" if config["gen1a_mode"] else f"{CR}disabled{C0}"}') + print( + f' {"Gen2 magic mode:":40}{f"{CG}enabled{C0}" if config["gen2_mode"] else f"{CR}disabled{C0}"}') + print( + f' {"Use anti-collision data from block 0:":40}{f"{CG}enabled{C0}" if config["block_anti_coll_mode"] else f"{CR}disabled{C0}"}') + print(f' {"Write mode:":40}{CY}{chameleon_cmd.MifareClassicWriteMode(config["write_mode"])}{C0}') print(f' LF: ' - f'{(self.get_slot_name(slot, chameleon_cmd.TagSenseType.TAG_SENSE_LF) + " - ") if args.extend else ""}' - f'{chameleon_cmd.TagSpecificType(data[chameleon_cmd.SlotNumber.to_fw(slot) * 2 + 1])}') - if args.extend == 2 or args.extend == 1 and enabled[chameleon_cmd.SlotNumber.to_fw(slot)]: - config = self.cmd.get_mf1_emulator_settings().data - print(' - Mifare Classic emulator settings:') - print(f' Detection (mfkey32) mode: {"enabled" if config[0] else "disabled"}') - print(f' Gen1A magic mode: {"enabled" if config[1] else "disabled"}') - print(f' Gen2 magic mode: {"enabled" if config[2] else "disabled"}') - print(f' Use anti-collision data from block 0: {"enabled" if config[3] else "disabled"}') - print(f' Write mode: {chameleon_cmd.MifareClassicWriteMode(config[4])}') + f'{(slotnames[fwslot]["lf"]["name"] if args.extend else ""):{maxnamelength+slotnames[fwslot]["lf"]["metalen"]+1 if args.extend else maxnamelength+1}}', end='') + print(f'{f"({CR}disabled{C0}) " if not enabled[fwslot]["lf"] else ""}', end='') + if lf_tag_type != chameleon_cmd.TagSpecificType.TAG_TYPE_UNDEFINED: + print(f"{CY if enabled[fwslot]['lf'] else C0}{lf_tag_type}{C0}") + else: + print("undef") @hw_slot.command('change', 'Set emulation tag slot activated.') @@ -1001,7 +1234,7 @@ def args_parser(self) -> ArgumentParserNoExit or None: # hw slot change -s 1 def on_exec(self, args: argparse.Namespace): slot_index = args.slot - self.cmd.set_slot_activated(slot_index) + self.cmd.set_active_slot(slot_index) print(f" - Set slot {slot_index} activated success.") @@ -1010,13 +1243,11 @@ class TagTypeRequiredUnit(DeviceRequiredUnit): def add_type_args(parser: ArgumentParserNoExit): type_choices = chameleon_cmd.TagSpecificType.list() help_str = "" - for t in chameleon_cmd.TagSpecificType: - if t == chameleon_cmd.TagSpecificType.TAG_TYPE_UNKNOWN: - continue + for t in type_choices: help_str += f"{t.value} = {t}, " help_str = help_str[:-2] parser.add_argument('-t', "--type", type=int, required=True, help=help_str, metavar="number", - choices=type_choices) + choices=[t.value for t in type_choices]) return parser def args_parser(self) -> ArgumentParserNoExit or None: @@ -1075,19 +1306,21 @@ def on_exec(self, args: argparse.Namespace): @hw_slot.command('enable', 'Set emulation tag slot enable or disable') -class HWSlotEnableSet(SlotIndexRequireUnit): +class HWSlotEnableSet(SlotIndexRequireUnit, SenseTypeRequireUnit): def args_parser(self) -> ArgumentParserNoExit or None: parser = ArgumentParserNoExit() self.add_slot_args(parser) + self.add_sense_type_args(parser) parser.add_argument('-e', '--enable', type=int, required=True, help="1 is Enable or 0 Disable", choices=[0, 1]) return parser - # hw slot enable -s 1 -e 0 + # hw slot enable -s 1 -st 0 -e 0 def on_exec(self, args: argparse.Namespace): slot_num = args.slot + sense_type = args.sense_type enable = args.enable - self.cmd.set_slot_enable(slot_num, enable) - print(f' - Set slot {slot_num} {"enable" if enable else "disable"} success.') + self.cmd.set_slot_enable(slot_num, sense_type, enable) + print(f' - Set slot {slot_num} {"LF" if sense_type==chameleon_cmd.TagSenseType.TAG_SENSE_LF else "HF"} {"enable" if enable else "disable"} success.') @lf_em_sim.command('set', 'Set simulated em410x card id') @@ -1099,8 +1332,7 @@ def args_parser(self) -> ArgumentParserNoExit or None: # lf em sim set --id 4545454545 def on_exec(self, args: argparse.Namespace): id_hex = args.id - id_bytes = bytearray.fromhex(id_hex) - self.cmd.set_em410x_sim_id(id_bytes) + self.cmd.em410x_set_emu_id(bytes.fromhex(id_hex)) print(' - Set em410x tag id success.') @@ -1111,9 +1343,9 @@ def args_parser(self) -> ArgumentParserNoExit or None: # lf em sim get def on_exec(self, args: argparse.Namespace): - response = self.cmd.get_em410x_sim_id() + response = self.cmd.em410x_get_emu_id() print(' - Get em410x tag id success.') - print(f'ID: {response.data.hex()}') + print(f'ID: {response.hex()}') @hw_slot_nick.command('set', 'Set tag nick name for slot') @@ -1133,7 +1365,7 @@ def on_exec(self, args: argparse.Namespace): encoded_name = name.encode(encoding="utf8") if len(encoded_name) > 32: raise ValueError("Your tag nick name too long.") - self.cmd.set_slot_tag_nick_name(slot_num, sense_type, encoded_name) + self.cmd.set_slot_tag_nick(slot_num, sense_type, encoded_name) print(f' - Set tag nick name for slot {slot_num} success.') @@ -1149,8 +1381,8 @@ def args_parser(self) -> ArgumentParserNoExit or None: def on_exec(self, args: argparse.Namespace): slot_num = args.slot sense_type = args.sense_type - res = self.cmd.get_slot_tag_nick_name(slot_num, sense_type) - print(f' - Get tag nick name for slot {slot_num}: {res.data.decode()}') + res = self.cmd.get_slot_tag_nick(slot_num, sense_type) + print(f' - Get tag nick name for slot {slot_num}: {res.decode(encoding="utf8")}') @hw_slot.command('update', 'Update config & data to device flash') @@ -1160,7 +1392,7 @@ def args_parser(self) -> ArgumentParserNoExit or None: # hw slot update def on_exec(self, args: argparse.Namespace): - self.cmd.update_slot_data_config() + self.cmd.slot_data_config_save() print(' - Update config and data from device memory to flash success.') @@ -1185,11 +1417,12 @@ def on_exec(self, args: argparse.Namespace): self.cmd.set_slot_data_default(slot, hf_type) self.cmd.set_slot_data_default(slot, lf_type) # finally, we can enable this slot. - self.cmd.set_slot_enable(slot, True) + self.cmd.set_slot_enable(slot, chameleon_cmd.TagSenseType.TAG_SENSE_HF, True) + self.cmd.set_slot_enable(slot, chameleon_cmd.TagSenseType.TAG_SENSE_LF, True) print(f' Slot {slot} setting done.') # update config and save to flash - self.cmd.update_slot_data_config() + self.cmd.slot_data_config_save() print(' - Succeeded opening all slots and setting data to default.') @@ -1201,7 +1434,7 @@ def args_parser(self) -> ArgumentParserNoExit or None: # hw dfu def on_exec(self, args: argparse.Namespace): print("Application restarting...") - self.cmd.enter_dfu_mode() + self.cmd.enter_bootloader() # In theory, after the above command is executed, the dfu mode will enter, and then the USB will restart, # To judge whether to enter the USB successfully, we only need to judge whether the USB becomes the VID and PID # of the DFU device. @@ -1218,12 +1451,12 @@ def args_parser(self) -> ArgumentParserNoExit or None: return None def on_exec(self, args: argparse.Namespace): - resp: chameleon_com.Response = self.cmd.get_settings_animation() - if resp.data[0] == 0: + resp = self.cmd.get_animation_mode() + if resp == 0: print("Full animation") - elif resp.data[0] == 1: + elif resp == 1: print("Minimal animation") - elif resp.data[0] == 2: + elif resp == 2: print("No animation") else: print("Unknown setting value, something failed.") @@ -1240,7 +1473,7 @@ def args_parser(self) -> ArgumentParserNoExit or None: def on_exec(self, args: argparse.Namespace): mode = args.mode - self.cmd.set_settings_animation(mode) + self.cmd.set_animation_mode(mode) print("Animation mode change success. Do not forget to store your settings in flash!") @@ -1251,8 +1484,7 @@ def args_parser(self) -> ArgumentParserNoExit or None: def on_exec(self, args: argparse.Namespace): print("Storing settings...") - resp: chameleon_com.Response = self.cmd.store_settings() - if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + if self.cmd.save_settings(): print(" - Store success @.@~") else: print(" - Store failed") @@ -1265,8 +1497,7 @@ def args_parser(self) -> ArgumentParserNoExit or None: def on_exec(self, args: argparse.Namespace): print("Initializing settings...") - resp: chameleon_com.Response = self.cmd.reset_settings() - if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + if self.cmd.reset_settings(): print(" - Reset success @.@~") else: print(" - Reset failed") @@ -1286,8 +1517,7 @@ def on_exec(self, args: argparse.Namespace): if not args.i_know_what_im_doing: print("This time your data's safe. Read the command documentation next time.") return - resp = self.cmd.factory_reset() - if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + if self.cmd.wipe_fds(): print(" - Reset successful! Please reconnect.") # let time for comm thread to close port time.sleep(0.1) @@ -1304,14 +1534,12 @@ def args_parser(self) -> ArgumentParserNoExit or None: return None def on_exec(self, args: argparse.Namespace): - resp = self.cmd.battery_information() - voltage = int.from_bytes(resp.data[:2], 'big') - percentage = resp.data[2] + voltage, percentage = self.cmd.get_battery_info() print(" - Battery information:") - print(f" voltage -> {voltage}mV") + print(f" voltage -> {voltage} mV") print(f" percentage -> {percentage}%") if percentage < HWBatteryInfo.BATTERY_LOW_LEVEL: - print(f"{colorama.Fore.RED}[!] Low battery, please charge.{colorama.Style.RESET_ALL}") + print(f"{CR}[!] Low battery, please charge.{C0}") @hw_settings_button_press.command('get', 'Get button press function of Button A and Button B.') @@ -1325,14 +1553,14 @@ def on_exec(self, args: argparse.Namespace): button_list = [chameleon_cmd.ButtonType.ButtonA, chameleon_cmd.ButtonType.ButtonB, ] print("") for button in button_list: - resp = self.cmd.get_button_press_fun(button) - resp_long = self.cmd.get_long_button_press_fun(button) - button_fn = chameleon_cmd.ButtonPressFunction.from_int(resp.data[0]) - button_long_fn = chameleon_cmd.ButtonPressFunction.from_int(resp_long.data[0]) - print(f" - {colorama.Fore.GREEN}{button} {colorama.Fore.YELLOW}short{colorama.Style.RESET_ALL}:" + resp = self.cmd.get_button_press_config(button) + resp_long = self.cmd.get_long_button_press_config(button) + button_fn = chameleon_cmd.ButtonPressFunction.from_int(resp) + button_long_fn = chameleon_cmd.ButtonPressFunction.from_int(resp_long) + print(f" - {CG}{button} {CY}short{C0}:" f" {button_fn}") print(f" usage: {button_fn.usage()}") - print(f" - {colorama.Fore.GREEN}{button} {colorama.Fore.YELLOW}long {colorama.Style.RESET_ALL}:" + print(f" - {CG}{button} {CY}long {C0}:" f" {button_long_fn}") print(f" usage: {button_long_fn.usage()}") print("") @@ -1359,9 +1587,9 @@ def on_exec(self, args: argparse.Namespace): button = chameleon_cmd.ButtonType.from_str(args.b) function = chameleon_cmd.ButtonPressFunction.from_int(args.f) if args.long: - self.cmd.set_long_button_press_fun(button, function) + self.cmd.set_long_button_press_config(button, function) else: - self.cmd.set_button_press_fun(button, function) + self.cmd.set_button_press_config(button, function) print(" - Successfully set button function to settings") @@ -1374,27 +1602,27 @@ def args_parser(self) -> ArgumentParserNoExit or None: return parser def on_exec(self, args: argparse.Namespace): - resp = self.cmd.get_ble_connect_key() + resp = self.cmd.get_ble_pairing_key() print(" - The current key of the device(ascii): " - f"{colorama.Fore.GREEN}{resp.data.decode(encoding='ascii')}{colorama.Style.RESET_ALL}") - + f"{CG}{resp.decode(encoding='ascii')}{C0}") + if args.key != None: if len(args.key) != 6: - print(f" - {colorama.Fore.RED}The ble connect key length must be 6{colorama.Style.RESET_ALL}") + print(f" - {CR}The ble connect key length must be 6{C0}") return if re.match(r'[0-9]{6}', args.key): self.cmd.set_ble_connect_key(args.key) print(" - Successfully set ble connect key to :", end='') - print(f"{colorama.Fore.GREEN}" + print(f"{CG}" f" { args.key }" - f"{colorama.Style.RESET_ALL}" + f"{C0}" ) else: - print(f" - {colorama.Fore.RED}Only 6 ASCII characters from 0 to 9 are supported.{colorama.Style.RESET_ALL}") + print(f" - {CR}Only 6 ASCII characters from 0 to 9 are supported.{C0}") @hw_settings.command('blepair', 'Check if BLE pairing is enabled, or set the enable switch for BLE pairing.') -class HWRaw(DeviceRequiredUnit): +class HWBlePair(DeviceRequiredUnit): def args_parser(self) -> ArgumentParserNoExit or None: parser = ArgumentParserNoExit() @@ -1404,24 +1632,24 @@ def args_parser(self) -> ArgumentParserNoExit or None: def on_exec(self, args: argparse.Namespace): is_pairing_enable = self.cmd.get_ble_pairing_enable() print(f" - Is ble pairing enable: ", end='') - color = colorama.Fore.GREEN if is_pairing_enable else colorama.Fore.RED + color = CG if is_pairing_enable else CR print( f"{color}" f"{ 'Yes' if is_pairing_enable else 'No' }" - f"{colorama.Style.RESET_ALL}" + f"{C0}" ) if args.enable is not None: if args.enable == 1 and is_pairing_enable: - print(f"{colorama.Fore.YELLOW} It is already in an enabled state.{colorama.Style.RESET_ALL}") + print(f"{CY} It is already in an enabled state.{C0}") return if args.enable == 0 and not is_pairing_enable: - print(f"{colorama.Fore.YELLOW} It is already in a non enabled state.{colorama.Style.RESET_ALL}") + print(f"{CY} It is already in a non enabled state.{C0}") return self.cmd.set_ble_pairing_enable(args.enable) print(f" - Successfully change ble pairing to " - f"{colorama.Fore.GREEN if args.enable else colorama.Fore.RED}" + f"{CG if args.enable else CR}" f"{ 'Enable' if args.enable else 'Disable' } " - f"{colorama.Style.RESET_ALL}" + f"{C0}" "state.") @@ -1443,11 +1671,91 @@ def args_parser(self) -> ArgumentParserNoExit or None: parser = ArgumentParserNoExit() parser.add_argument('-c', '--command', type=int, required=True, help="Command (Int) to send") parser.add_argument('-d', '--data', type=str, help="Data (HEX) to send", default="") + parser.add_argument('-t', '--timeout', type=int, help="Timeout in seconds", default=3) return parser def on_exec(self, args: argparse.Namespace): - response = self.cmd.device.send_cmd_sync(args.command, data=bytes.fromhex(args.data), status=0x0) + response = self.cmd.device.send_cmd_sync( + args.command, data=bytes.fromhex(args.data), status=0x0, timeout=args.timeout) print(" - Received:") print(f" Command: {response.cmd}") - print(f" Status: {response.status}") + status_string = f" Status: {response.status:#02x}" + if response.status in chameleon_status.Device: + status_string += f" {chameleon_status.Device[response.status]}" + if response.status in chameleon_status.message: + status_string += f": {chameleon_status.message[response.status]}" + print(status_string) + else: + print(f" Status: {response.status:#02x}") print(f" Data (HEX): {response.data.hex()}") + + +@hf_14a.command('raw', 'Send raw command') +class HF14ARaw(ReaderRequiredUnit): + + def bool_to_bit(self, value): + return 1 if value else 0 + + def args_parser(self) -> ArgumentParserNoExit or None: + parser = ArgumentParserNoExit() + parser.add_argument('-a', '--activate-rf', help="Active signal field ON without select", + action='store_true', default=False,) + parser.add_argument('-s', '--select-tag', help="Active signal field ON with select", + action='store_true', default=False,) + # TODO: parser.add_argument('-3', '--type3-select-tag', help="Active signal field ON with ISO14443-3 select (no RATS)", action='store_true', default=False,) + parser.add_argument('-d', '--data', type=str, help="Data to be sent") + parser.add_argument('-b', '--bits', type=int, help="Number of bits to send. Useful for send partial byte") + parser.add_argument('-c', '--crc', help="Calculate and append CRC", action='store_true', default=False,) + parser.add_argument('-r', '--response', help="Do not read response", action='store_true', default=False,) + parser.add_argument('-cc', '--crc-clear', help="Verify and clear CRC of received data", + action='store_true', default=False,) + parser.add_argument('-k', '--keep-rf', help="Keep signal field ON after receive", + action='store_true', default=False,) + parser.add_argument('-t', '--timeout', type=int, help="Timeout in ms", default=100) + # TODO: need support for carriage returns in parser, why are they mangled? + # parser.description = 'Examples:\n' \ + # ' hf 14a raw -b 7 -d 40 -k\n' \ + # ' hf 14a raw -d 43 -k\n' \ + # ' hf 14a raw -d 3000 -c\n' \ + # ' hf 14a raw -sc -d 6000\n' + return parser + + def on_exec(self, args: argparse.Namespace): + options = { + 'activate_rf_field': self.bool_to_bit(args.activate_rf), + 'wait_response': self.bool_to_bit(not args.response), + 'append_crc': self.bool_to_bit(args.crc), + 'auto_select': self.bool_to_bit(args.select_tag), + 'keep_rf_field': self.bool_to_bit(args.keep_rf), + 'check_response_crc': self.bool_to_bit(args.crc_clear), + # 'auto_type3_select': self.bool_to_bit(args.type3-select-tag), + } + data: str = args.data + if data is not None: + data = data.replace(' ', '') + if re.match(r'^[0-9a-fA-F]+$', data): + if len(data) % 2 != 0: + print(f" [!] {CR}The length of the data must be an integer multiple of 2.{C0}") + return + else: + data_bytes = bytes.fromhex(data) + else: + print(f" [!] {CR}The data must be a HEX string{C0}") + return + else: + data_bytes = [] + if args.bits is not None and args.crc: + print(f" [!] {CR}--bits and --crc are mutually exclusive{C0}") + return + + # Exec 14a raw cmd. + resp = self.cmd.hf14a_raw(options, args.timeout, data_bytes, args.bits) + if len(resp) > 0: + print( + # print head + " - " + + # print data + ' '.join([hex(byte).replace('0x', '').rjust(2, '0') for byte in resp]) + ) + else: + print(F" [*] {CY}No response{C0}") diff --git a/software/script/chameleon_cmd.py b/software/script/chameleon_cmd.py index 2ea7ffd5..0e79eceb 100644 --- a/software/script/chameleon_cmd.py +++ b/software/script/chameleon_cmd.py @@ -1,5 +1,6 @@ import enum import struct +import ctypes import chameleon_com import chameleon_status @@ -8,9 +9,9 @@ CURRENT_VERSION_SETTINGS = 5 DATA_CMD_GET_APP_VERSION = 1000 -DATA_CMD_CHANGE_MODE = 1001 +DATA_CMD_CHANGE_DEVICE_MODE = 1001 DATA_CMD_GET_DEVICE_MODE = 1002 -DATA_CMD_SET_SLOT_ACTIVATED = 1003 +DATA_CMD_SET_ACTIVE_SLOT = 1003 DATA_CMD_SET_SLOT_TAG_TYPE = 1004 DATA_CMD_SET_SLOT_DATA_DEFAULT = 1005 DATA_CMD_SET_SLOT_ENABLE = 1006 @@ -47,53 +48,57 @@ DATA_CMD_GET_LONG_BUTTON_PRESS_CONFIG = 1028 DATA_CMD_SET_LONG_BUTTON_PRESS_CONFIG = 1029 -DATA_CMD_SET_BLE_CONNECT_KEY_CONFIG = 1030 -DATA_CMD_GET_BLE_CONNECT_KEY_CONFIG = 1031 - +DATA_CMD_SET_BLE_PAIRING_KEY = 1030 +DATA_CMD_GET_BLE_PAIRING_KEY = 1031 DATA_CMD_DELETE_ALL_BLE_BONDS = 1032 -DATA_CMD_GET_DEVICE = 1033 -DATA_CMD_GET_SETTINGS = 1034 +DATA_CMD_GET_DEVICE_MODEL = 1033 +# FIXME: implemented but unused in CLI commands +DATA_CMD_GET_DEVICE_SETTINGS = 1034 DATA_CMD_GET_DEVICE_CAPABILITIES = 1035 DATA_CMD_GET_BLE_PAIRING_ENABLE = 1036 DATA_CMD_SET_BLE_PAIRING_ENABLE = 1037 -DATA_CMD_SCAN_14A_TAG = 2000 -DATA_CMD_MF1_SUPPORT_DETECT = 2001 -DATA_CMD_MF1_NT_LEVEL_DETECT = 2002 -DATA_CMD_MF1_DARKSIDE_DETECT = 2003 +DATA_CMD_HF14A_SCAN = 2000 +DATA_CMD_MF1_DETECT_SUPPORT = 2001 +DATA_CMD_MF1_DETECT_PRNG = 2002 +DATA_CMD_MF1_STATIC_NESTED_ACQUIRE = 2003 DATA_CMD_MF1_DARKSIDE_ACQUIRE = 2004 -DATA_CMD_MF1_NT_DIST_DETECT = 2005 +DATA_CMD_MF1_DETECT_NT_DIST = 2005 DATA_CMD_MF1_NESTED_ACQUIRE = 2006 -DATA_CMD_MF1_CHECK_ONE_KEY_BLOCK = 2007 +DATA_CMD_MF1_AUTH_ONE_KEY_BLOCK = 2007 DATA_CMD_MF1_READ_ONE_BLOCK = 2008 DATA_CMD_MF1_WRITE_ONE_BLOCK = 2009 - -DATA_CMD_SCAN_EM410X_TAG = 3000 -DATA_CMD_WRITE_EM410X_TO_T5577 = 3001 - -DATA_CMD_LOAD_MF1_EMU_BLOCK_DATA = 4000 -DATA_CMD_SET_MF1_ANTI_COLLISION_RES = 4001 - -DATA_CMD_SET_MF1_DETECTION_ENABLE = 4004 -DATA_CMD_GET_MF1_DETECTION_COUNT = 4005 -DATA_CMD_GET_MF1_DETECTION_RESULT = 4006 - -DATA_CMD_READ_MF1_EMU_BLOCK_DATA = 4008 - -DATA_CMD_GET_MF1_EMULATOR_CONFIG = 4009 -DATA_CMD_GET_MF1_GEN1A_MODE = 4010 -DATA_CMD_SET_MF1_GEN1A_MODE = 4011 -DATA_CMD_GET_MF1_GEN2_MODE = 4012 -DATA_CMD_SET_MF1_GEN2_MODE = 4013 -DATA_CMD_GET_MF1_USE_FIRST_BLOCK_COLL = 4014 -DATA_CMD_SET_MF1_USE_FIRST_BLOCK_COLL = 4015 -DATA_CMD_GET_MF1_WRITE_MODE = 4016 -DATA_CMD_SET_MF1_WRITE_MODE = 4017 -DATA_CMD_GET_MF1_ANTI_COLL_DATA = 4018 - -DATA_CMD_SET_EM410X_EMU_ID = 5000 -DATA_CMD_GET_EM410X_EMU_ID = 5001 +DATA_CMD_HF14A_RAW = 2010 + +DATA_CMD_EM410X_SCAN = 3000 +DATA_CMD_EM410X_WRITE_TO_T55XX = 3001 + +DATA_CMD_MF1_WRITE_EMU_BLOCK_DATA = 4000 +DATA_CMD_HF14A_SET_ANTI_COLL_DATA = 4001 +DATA_CMD_MF1_SET_DETECTION_ENABLE = 4004 +DATA_CMD_MF1_GET_DETECTION_COUNT = 4005 +DATA_CMD_MF1_GET_DETECTION_LOG = 4006 +# FIXME: not implemented +DATA_CMD_MF1_GET_DETECTION_ENABLE = 4007 +DATA_CMD_MF1_READ_EMU_BLOCK_DATA = 4008 +DATA_CMD_MF1_GET_EMULATOR_CONFIG = 4009 +# FIXME: not implemented +DATA_CMD_MF1_GET_GEN1A_MODE = 4010 +DATA_CMD_MF1_SET_GEN1A_MODE = 4011 +# FIXME: not implemented +DATA_CMD_MF1_GET_GEN2_MODE = 4012 +DATA_CMD_MF1_SET_GEN2_MODE = 4013 +# FIXME: not implemented +DATA_CMD_MF1_GET_BLOCK_ANTI_COLL_MODE = 4014 +DATA_CMD_MF1_SET_BLOCK_ANTI_COLL_MODE = 4015 +# FIXME: not implemented +DATA_CMD_MF1_GET_WRITE_MODE = 4016 +DATA_CMD_MF1_SET_WRITE_MODE = 4017 +DATA_CMD_HF14A_GET_ANTI_COLL_DATA = 4018 + +DATA_CMD_EM410X_SET_EMU_ID = 5000 +DATA_CMD_EM410X_GET_EMU_ID = 5001 @enum.unique @@ -144,29 +149,92 @@ def __str__(self): @enum.unique class TagSpecificType(enum.IntEnum): - # Empty slot - TAG_TYPE_UNKNOWN = 0 - # 125 kHz (id) cards - TAG_TYPE_EM410X = 1 - # Mifare Classic - TAG_TYPE_MIFARE_Mini = 2 - TAG_TYPE_MIFARE_1024 = 3 - TAG_TYPE_MIFARE_2048 = 4 - TAG_TYPE_MIFARE_4096 = 5 - # NTAG - TAG_TYPE_NTAG_213 = 6 - TAG_TYPE_NTAG_215 = 7 - TAG_TYPE_NTAG_216 = 8 + TAG_TYPE_UNDEFINED = 0, + + # old HL/LF common types, slots using these ones need to be migrated first + OLD_TAG_TYPE_EM410X = 1, + OLD_TAG_TYPE_MIFARE_Mini = 2, + OLD_TAG_TYPE_MIFARE_1024 = 3, + OLD_TAG_TYPE_MIFARE_2048 = 4, + OLD_TAG_TYPE_MIFARE_4096 = 5, + OLD_TAG_TYPE_NTAG_213 = 6, + OLD_TAG_TYPE_NTAG_215 = 7, + OLD_TAG_TYPE_NTAG_216 = 8, + OLD_TAG_TYPES_END = 9, + + ###### LF ###### + + #### ASK Tag-Talk-First 100 #### + # EM410x + TAG_TYPE_EM410X = 100, + # FDX-B + # securakey + # gallagher + # PAC/Stanley + # Presco + # Visa2000 + # Viking + # Noralsy + # Jablotron + + #### FSK Tag-Talk-First 200 #### + # HID Prox + # ioProx + # AWID + # Paradox + + #### PSK Tag-Talk-First 300 #### + # Indala + # Keri + # NexWatch + + #### Reader-Talk-First 400 #### + # T5577 + # EM4x05/4x69 + # EM4x50/4x70 + # Hitag series + + TAG_TYPES_LF_END = 999, + + ###### HF ###### + + #### MIFARE Classic series 1000 #### + TAG_TYPE_MIFARE_Mini = 1000, + TAG_TYPE_MIFARE_1024 = 1001, + TAG_TYPE_MIFARE_2048 = 1002, + TAG_TYPE_MIFARE_4096 = 1003, + #### MFUL / NTAG series 1100 #### + TAG_TYPE_NTAG_213 = 1100, + TAG_TYPE_NTAG_215 = 1101, + TAG_TYPE_NTAG_216 = 1102, + #### MIFARE Plus series 1200 #### + #### DESFire series 1300 #### + + #### ST25TA series 2000 #### + + #### HF14A-4 series 3000 #### @staticmethod - def list(exclude_unknown=True): - enum_list = list(map(int, TagSpecificType)) - if exclude_unknown: - enum_list.remove(TagSpecificType.TAG_TYPE_UNKNOWN) - return enum_list + def list(exclude_meta=True): + return [t for t in TagSpecificType + if (t > TagSpecificType.OLD_TAG_TYPES_END and + t != TagSpecificType.TAG_TYPES_LF_END) + or not exclude_meta] + + @staticmethod + def list_hf(): + return [t for t in TagSpecificType.list() + if (t > TagSpecificType.TAG_TYPES_LF_END)] + + @staticmethod + def list_lf(): + return [t for t in TagSpecificType.list() + if (TagSpecificType.TAG_TYPE_UNDEFINED < t < TagSpecificType.TAG_TYPES_LF_END)] def __str__(self): - if self == TagSpecificType.TAG_TYPE_EM410X: + if self == TagSpecificType.TAG_TYPE_UNDEFINED: + return "Undefined" + elif self == TagSpecificType.TAG_TYPE_EM410X: return "EM410X" elif self == TagSpecificType.TAG_TYPE_MIFARE_Mini: return "Mifare Mini" @@ -182,7 +250,9 @@ def __str__(self): return "NTAG 215" elif self == TagSpecificType.TAG_TYPE_NTAG_216: return "NTAG 216" - return "Unknown" + elif self < TagSpecificType.OLD_TAG_TYPES_END: + return "Old tag type, must be migrated! Upgrade fw!" + return "Invalid" @enum.unique @@ -195,6 +265,8 @@ class MifareClassicWriteMode(enum.IntEnum): DECEIVE = 2 # Store data to RAM, but not to ROM SHADOW = 3 + # Shadow requested, will be changed to SHADOW and stored to ROM + SHADOW_REQ = 4 @staticmethod def list(): @@ -209,6 +281,61 @@ def __str__(self): return "Deceive" elif self == MifareClassicWriteMode.SHADOW: return "Shadow" + elif self == MifareClassicWriteMode.SHADOW_REQ: + return "Shadow requested" + return "None" + + +@enum.unique +class MifareClassicPrngType(enum.IntEnum): + # the random number of the card response is fixed + STATIC = 0 + # the random number of the card response is weak + WEAK = 1 + # the random number of the card response is unpredictable + HARD = 2 + + @staticmethod + def list(): + return list(map(int, MifareClassicPrngType)) + + def __str__(self): + if self == MifareClassicPrngType.STATIC: + return "Static" + elif self == MifareClassicPrngType.WEAK: + return "Weak" + elif self == MifareClassicPrngType.HARD: + return "Hard" + return "None" + + +@enum.unique +class MifareClassicDarksideStatus(enum.IntEnum): + OK = 0 + # Darkside can't fix NT (PRNG is unpredictable) + CANT_FIX_NT = 1 + # Darkside try to recover a default key + LUCKY_AUTH_OK = 2 + # Darkside can't get tag response enc(nak) + NO_NAK_SENT = 3 + # Darkside running, can't change tag + TAG_CHANGED = 4 + + @staticmethod + def list(): + return list(map(int, MifareClassicDarksideStatus)) + + def __str__(self): + if self == MifareClassicDarksideStatus.OK: + return "Success" + elif self == MifareClassicDarksideStatus.CANT_FIX_NT: + return "Cannot fix NT (unpredictable PRNG)" + elif self == MifareClassicDarksideStatus.LUCKY_AUTH_OK: + return "Try to recover a default key" + elif self == MifareClassicDarksideStatus.NO_NAK_SENT: + return "Cannot get tag response enc(nak)" + elif self == MifareClassicDarksideStatus.TAG_CHANGED: + return "Tag changed during attack" return "None" @@ -292,109 +419,146 @@ def __init__(self, chameleon: chameleon_com.ChameleonCom): :param chameleon: chameleon instance, @see chameleon_device.Chameleon """ self.device = chameleon - if not len(self.device.commands): - self.get_device_capabilities() - def get_firmware_version(self) -> int: + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def get_app_version(self): """ Get firmware version number(application) """ - resp = self.device.send_cmd_sync(DATA_CMD_GET_APP_VERSION, 0x00) - return int.from_bytes(resp.data, 'little') + resp = self.device.send_cmd_sync(DATA_CMD_GET_APP_VERSION) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + resp.data = struct.unpack('!BB', resp.data) + # older protocol, must upgrade! + if resp.status == 0 and resp.data == b'\x00\x01': + print("Chameleon does not understand new protocol. Please update firmware") + return chameleon_com.Response(cmd=DATA_CMD_GET_APP_VERSION, + status=chameleon_status.Device.STATUS_NOT_IMPLEMENTED) + return resp - def get_device_chip_id(self) -> str: + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def get_device_chip_id(self): """ Get device chip id """ - resp = self.device.send_cmd_sync(DATA_CMD_GET_DEVICE_CHIP_ID, 0x00) - return resp.data.hex() + resp = self.device.send_cmd_sync(DATA_CMD_GET_DEVICE_CHIP_ID) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + resp.data = resp.data.hex() + return resp - def get_device_address(self) -> str: + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def get_device_address(self): """ Get device address """ - resp = self.device.send_cmd_sync(DATA_CMD_GET_DEVICE_ADDRESS, 0x00) - return resp.data[::-1].hex() + resp = self.device.send_cmd_sync(DATA_CMD_GET_DEVICE_ADDRESS) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + resp.data = resp.data.hex() + return resp + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) def get_git_version(self) -> str: - resp = self.device.send_cmd_sync(DATA_CMD_GET_GIT_VERSION, 0x00) - return resp.data.decode('utf-8') + resp = self.device.send_cmd_sync(DATA_CMD_GET_GIT_VERSION) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + resp.data = resp.data.decode('utf-8') + return resp + + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def get_device_mode(self): + resp = self.device.send_cmd_sync(DATA_CMD_GET_DEVICE_MODE) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + resp.data, = struct.unpack('!?', resp.data) + return resp - def is_reader_device_mode(self) -> bool: + def is_device_reader_mode(self) -> bool: """ Get device mode, reader or tag :return: True is reader mode, else tag mode """ - resp = self.device.send_cmd_sync(DATA_CMD_GET_DEVICE_MODE, 0x00) - return True if resp.data[0] == 1 else False + return self.get_device_mode() # Note: Will return NOT_IMPLEMENTED if one tries to set reader mode on Lite @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) - def set_reader_device_mode(self, reader_mode: bool = True): + def change_device_mode(self, mode): + data = struct.pack('!B', mode) + return self.device.send_cmd_sync(DATA_CMD_CHANGE_DEVICE_MODE, data) + + def set_device_reader_mode(self, reader_mode: bool = True): """ Change device mode, reader or tag :param reader_mode: True if reader mode, False if tag mode. :return: """ - return self.device.send_cmd_sync(DATA_CMD_CHANGE_MODE, 0x00, 0x0001 if reader_mode else 0x0000) + self.change_device_mode(reader_mode) @expect_response(chameleon_status.Device.HF_TAG_OK) - def scan_tag_14a(self): + def hf14a_scan(self): """ 14a tags in the scanning field :return: """ - return self.device.send_cmd_sync(DATA_CMD_SCAN_14A_TAG, 0x00) - - def detect_mf1_support(self): - """ - Detect whether it is mifare classic label + resp = self.device.send_cmd_sync(DATA_CMD_HF14A_SCAN) + if resp.status == chameleon_status.Device.HF_TAG_OK: + # uidlen[1]|uid[uidlen]|atqa[2]|sak[1]|atslen[1]|ats[atslen] + offset = 0 + data = [] + while offset < len(resp.data): + uidlen, = struct.unpack_from('!B', resp.data, offset) + offset += struct.calcsize('!B') + uid, atqa, sak, atslen = struct.unpack_from(f'!{uidlen}s2s1sB', resp.data, offset) + offset += struct.calcsize(f'!{uidlen}s2s1sB') + ats, = struct.unpack_from(f'!{atslen}s', resp.data, offset) + offset += struct.calcsize(f'!{atslen}s') + data.append({'uid': uid, 'atqa': atqa, 'sak': sak, 'ats': ats}) + resp.data = data + return resp + + def mf1_detect_support(self): + """ + Detect whether it is mifare classic tag :return: """ - return self.device.send_cmd_sync(DATA_CMD_MF1_SUPPORT_DETECT, 0x00) + resp = self.device.send_cmd_sync(DATA_CMD_MF1_DETECT_SUPPORT) + return resp.status == chameleon_status.Device.HF_TAG_OK - def detect_mf1_nt_level(self): + @expect_response(chameleon_status.Device.HF_TAG_OK) + def mf1_detect_prng(self): """ detect mifare Class of classic nt vulnerabilities :return: """ - return self.device.send_cmd_sync(DATA_CMD_MF1_NT_LEVEL_DETECT, 0x00) - - def detect_darkside_support(self): - """ - Check if the card is vulnerable to mifare classic darkside attack - :return: - """ - return self.device.send_cmd_sync(DATA_CMD_MF1_DARKSIDE_DETECT, 0x00, None, timeout=20) + resp = self.device.send_cmd_sync(DATA_CMD_MF1_DETECT_PRNG) + if resp.status == chameleon_status.Device.HF_TAG_OK: + resp.data = resp.data[0] + return resp @expect_response(chameleon_status.Device.HF_TAG_OK) - def detect_nt_distance(self, block_known, type_known, key_known): + def mf1_detect_nt_dist(self, block_known, type_known, key_known): """ Detect the random number distance of the card :return: """ - data = bytearray() - data.append(type_known) - data.append(block_known) - data.extend(key_known) - return self.device.send_cmd_sync(DATA_CMD_MF1_NT_DIST_DETECT, 0x00, data) + data = struct.pack('!BB6s', type_known, block_known, key_known) + resp = self.device.send_cmd_sync(DATA_CMD_MF1_DETECT_NT_DIST, data) + if resp.status == chameleon_status.Device.HF_TAG_OK: + uid, dist = struct.unpack('!II', resp.data) + resp.data = {'uid': uid, 'dist': dist} + return resp @expect_response(chameleon_status.Device.HF_TAG_OK) - def acquire_nested(self, block_known, type_known, key_known, block_target, type_target): + def mf1_nested_acquire(self, block_known, type_known, key_known, block_target, type_target): """ Collect the key NT parameters needed for Nested decryption :return: """ - data = bytearray() - data.append(type_known) - data.append(block_known) - data.extend(key_known) - data.append(type_target) - data.append(block_target) - return self.device.send_cmd_sync(DATA_CMD_MF1_NESTED_ACQUIRE, 0x00, data) + data = struct.pack('!BB6sBB', type_known, block_known, key_known, type_target, block_target) + resp = self.device.send_cmd_sync(DATA_CMD_MF1_NESTED_ACQUIRE, data) + if resp.status == chameleon_status.Device.HF_TAG_OK: + resp.data = [{'nt': nt, 'nt_enc': nt_enc, 'par': par} + for nt, nt_enc, par in struct.iter_unpack('!IIB', resp.data)] + return resp @expect_response(chameleon_status.Device.HF_TAG_OK) - def acquire_darkside(self, block_target, type_target, first_recover: int or bool, sync_max): + def mf1_darkside_acquire(self, block_target, type_target, first_recover: int or bool, sync_max): """ Collect the key parameters needed for Darkside decryption :param block_target: @@ -403,17 +567,18 @@ def acquire_darkside(self, block_target, type_target, first_recover: int or bool :param sync_max: :return: """ - data = bytearray() - data.append(type_target) - data.append(block_target) - if isinstance(first_recover, bool): - first_recover = 0x01 if first_recover else 0x00 - data.append(first_recover) - data.append(sync_max) - return self.device.send_cmd_sync(DATA_CMD_MF1_DARKSIDE_ACQUIRE, 0x00, data, timeout=sync_max + 5) + data = struct.pack('!BBBB', type_target, block_target, first_recover, sync_max) + resp = self.device.send_cmd_sync(DATA_CMD_MF1_DARKSIDE_ACQUIRE, data, timeout=sync_max * 10) + if resp.status == chameleon_status.Device.HF_TAG_OK: + if resp.data[0] == MifareClassicDarksideStatus.OK: + darkside_status, uid, nt1, par, ks1, nr, ar = struct.unpack('!BIIQQII', resp.data) + resp.data = (darkside_status, {'uid': uid, 'nt1': nt1, 'par': par, 'ks1': ks1, 'nr': nr, 'ar': ar}) + else: + resp.data = (resp.data[0],) + return resp @expect_response([chameleon_status.Device.HF_TAG_OK, chameleon_status.Device.MF_ERR_AUTH]) - def auth_mf1_key(self, block, type_value, key): + def mf1_auth_one_key_block(self, block, type_value, key): """ Verify the mf1 key, only verify the specified type of key for a single sector :param block: @@ -421,14 +586,13 @@ def auth_mf1_key(self, block, type_value, key): :param key: :return: """ - data = bytearray() - data.append(type_value) - data.append(block) - data.extend(key) - return self.device.send_cmd_sync(DATA_CMD_MF1_CHECK_ONE_KEY_BLOCK, 0x00, data) + data = struct.pack('!BB6s', type_value, block, key) + resp = self.device.send_cmd_sync(DATA_CMD_MF1_AUTH_ONE_KEY_BLOCK, data) + resp.data = resp.status == chameleon_status.Device.HF_TAG_OK + return resp @expect_response(chameleon_status.Device.HF_TAG_OK) - def read_mf1_block(self, block, type_value, key): + def mf1_read_one_block(self, block, type_value, key): """ read one mf1 block :param block: @@ -436,14 +600,11 @@ def read_mf1_block(self, block, type_value, key): :param key: :return: """ - data = bytearray() - data.append(type_value) - data.append(block) - data.extend(key) - return self.device.send_cmd_sync(DATA_CMD_MF1_READ_ONE_BLOCK, 0x00, data) + data = struct.pack('!BB6s', type_value, block, key) + return self.device.send_cmd_sync(DATA_CMD_MF1_READ_ONE_BLOCK, data) @expect_response(chameleon_status.Device.HF_TAG_OK) - def write_mf1_block(self, block, type_value, key, block_data): + def mf1_write_one_block(self, block, type_value, key, block_data): """ Write mf1 single block :param block: @@ -452,64 +613,128 @@ def write_mf1_block(self, block, type_value, key, block_data): :param block_data: :return: """ - data = bytearray() - data.append(type_value) - data.append(block) - data.extend(key) - data.extend(block_data) - return self.device.send_cmd_sync(DATA_CMD_MF1_WRITE_ONE_BLOCK, 0x00, data) + data = struct.pack('!BB6s16s', type_value, block, key, block_data) + resp = self.device.send_cmd_sync(DATA_CMD_MF1_WRITE_ONE_BLOCK, data) + resp.data = resp.status == chameleon_status.Device.HF_TAG_OK + return resp + + @expect_response(chameleon_status.Device.HF_TAG_OK) + def hf14a_raw(self, options, resp_timeout_ms=100, data=[], bitlen=None): + """ + Send raw cmd to 14a tag + :param options: + :param resp_timeout_ms: + :param data: + :param bit_owned_by_the_last_byte: + :return: + """ + + class CStruct(ctypes.BigEndianStructure): + _fields_ = [ + ("activate_rf_field", ctypes.c_uint8, 1), + ("wait_response", ctypes.c_uint8, 1), + ("append_crc", ctypes.c_uint8, 1), + ("auto_select", ctypes.c_uint8, 1), + ("keep_rf_field", ctypes.c_uint8, 1), + ("check_response_crc", ctypes.c_uint8, 1), + ("reserved", ctypes.c_uint8, 2), + ] + + cs = CStruct() + cs.activate_rf_field = options['activate_rf_field'] + cs.wait_response = options['wait_response'] + cs.append_crc = options['append_crc'] + cs.auto_select = options['auto_select'] + cs.keep_rf_field = options['keep_rf_field'] + cs.check_response_crc = options['check_response_crc'] + + if bitlen is None: + bitlen = len(data) * 8 # bits = bytes * 8(bit) + else: + if len(data) == 0: + raise ValueError(f'bitlen={bitlen} but missing data') + if not ((len(data) - 1) * 8 < bitlen <= len(data) * 8): + raise ValueError(f'bitlen={bitlen} incompatible with provided data ({len(data)} bytes), ' + f'must be between {((len(data) - 1) * 8 )+1} and {len(data) * 8} included') + + data = bytes(cs)+struct.pack(f'!HH{len(data)}s', resp_timeout_ms, bitlen, bytearray(data)) + return self.device.send_cmd_sync(DATA_CMD_HF14A_RAW, data, timeout=(resp_timeout_ms / 1000) + 1) + + @expect_response(chameleon_status.Device.HF_TAG_OK) + def mf1_static_nested_acquire(self, block_known, type_known, key_known, block_target, type_target): + """ + Collect the key NT parameters needed for StaticNested decryption + :return: + """ + data = struct.pack('!BB6sBB', type_known, block_known, key_known, type_target, block_target) + resp = self.device.send_cmd_sync(DATA_CMD_MF1_STATIC_NESTED_ACQUIRE, data) + if resp.status == chameleon_status.Device.HF_TAG_OK: + resp.data = { + 'uid': struct.unpack('!I', resp.data[0:4])[0], + 'nts': [ + { + 'nt': nt, + 'nt_enc': nt_enc + } for nt, nt_enc in struct.iter_unpack('!II', resp.data[4:]) + ] + } + return resp @expect_response(chameleon_status.Device.LF_TAG_OK) - def read_em_410x(self): + def em410x_scan(self): """ Read the card number of EM410X :return: """ - return self.device.send_cmd_sync(DATA_CMD_SCAN_EM410X_TAG, 0x00) + return self.device.send_cmd_sync(DATA_CMD_EM410X_SCAN) @expect_response(chameleon_status.Device.LF_TAG_OK) - def write_em_410x_to_t55xx(self, id_bytes: bytearray): + def em410x_write_to_t55xx(self, id_bytes: bytes): """ Write EM410X card number into T55XX :param id_bytes: ID card number :return: """ - new_key = [0x20, 0x20, 0x66, 0x66] - old_keys = [[0x51, 0x24, 0x36, 0x48], [0x19, 0x92, 0x04, 0x27]] + new_key = b'\x20\x20\x66\x66' + old_keys = [b'\x51\x24\x36\x48', b'\x19\x92\x04\x27'] if len(id_bytes) != 5: raise ValueError("The id bytes length must equal 5") - data = bytearray() - data.extend(id_bytes) - data.extend(new_key) - for key in old_keys: - data.extend(key) - return self.device.send_cmd_sync(DATA_CMD_WRITE_EM410X_TO_T5577, 0x00, data) + data = struct.pack(f'!5s4s{4*len(old_keys)}s', id_bytes, new_key, b''.join(old_keys)) + return self.device.send_cmd_sync(DATA_CMD_EM410X_WRITE_TO_T55XX, data) + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) def get_slot_info(self): """ Get slots info :return: """ - return self.device.send_cmd_sync(DATA_CMD_GET_SLOT_INFO, 0x00) + resp = self.device.send_cmd_sync(DATA_CMD_GET_SLOT_INFO) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + resp.data = [{'hf': hf, 'lf': lf} + for hf, lf in struct.iter_unpack('!HH', resp.data)] + return resp + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) def get_active_slot(self): """ Get selected slot :return: """ - return self.device.send_cmd_sync(DATA_CMD_GET_ACTIVE_SLOT, 0x00) + resp = self.device.send_cmd_sync(DATA_CMD_GET_ACTIVE_SLOT) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + resp.data = resp.data[0] + return resp @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) - def set_slot_activated(self, slot_index: SlotNumber): + def set_active_slot(self, slot_index: SlotNumber): """ Set the card slot currently active for use :param slot_index: Card slot index :return: """ # SlotNumber() will raise error for us if slot_index not in slot range - data = bytearray() - data.append(SlotNumber.to_fw(slot_index)) - return self.device.send_cmd_sync(DATA_CMD_SET_SLOT_ACTIVATED, 0x00, data) + data = struct.pack('!B', SlotNumber.to_fw(slot_index)) + return self.device.send_cmd_sync(DATA_CMD_SET_ACTIVE_SLOT, data) @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) def set_slot_tag_type(self, slot_index: SlotNumber, tag_type: TagSpecificType): @@ -522,10 +747,8 @@ def set_slot_tag_type(self, slot_index: SlotNumber, tag_type: TagSpecificType): :return: """ # SlotNumber() will raise error for us if slot_index not in slot range - data = bytearray() - data.append(SlotNumber.to_fw(slot_index)) - data.append(tag_type) - return self.device.send_cmd_sync(DATA_CMD_SET_SLOT_TAG_TYPE, 0x00, data) + data = struct.pack('!BH', SlotNumber.to_fw(slot_index), tag_type) + return self.device.send_cmd_sync(DATA_CMD_SET_SLOT_TAG_TYPE, data) @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) def delete_slot_sense_type(self, slot_index: SlotNumber, sense_type: TagSenseType): @@ -535,8 +758,8 @@ def delete_slot_sense_type(self, slot_index: SlotNumber, sense_type: TagSenseTyp :param sense_type: Sense type to disable :return: """ - return self.device.send_cmd_sync(DATA_CMD_DELETE_SLOT_SENSE_TYPE, 0x00, - bytearray([SlotNumber.to_fw(slot_index), sense_type])) + data = struct.pack('!BB', SlotNumber.to_fw(slot_index), sense_type) + return self.device.send_cmd_sync(DATA_CMD_DELETE_SLOT_SENSE_TYPE, data) @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) def set_slot_data_default(self, slot_index: SlotNumber, tag_type: TagSpecificType): @@ -548,13 +771,11 @@ def set_slot_data_default(self, slot_index: SlotNumber, tag_type: TagSpecificTyp :return: """ # SlotNumber() will raise error for us if slot_index not in slot range - data = bytearray() - data.append(SlotNumber.to_fw(slot_index)) - data.append(tag_type) - return self.device.send_cmd_sync(DATA_CMD_SET_SLOT_DATA_DEFAULT, 0x00, data) + data = struct.pack('!BH', SlotNumber.to_fw(slot_index), tag_type) + return self.device.send_cmd_sync(DATA_CMD_SET_SLOT_DATA_DEFAULT, data) @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) - def set_slot_enable(self, slot_index: SlotNumber, enable: bool): + def set_slot_enable(self, slot_index: SlotNumber, sense_type: TagSenseType, enabled: bool): """ Set whether the specified card slot is enabled :param slot_index: Card slot number @@ -562,59 +783,79 @@ def set_slot_enable(self, slot_index: SlotNumber, enable: bool): :return: """ # SlotNumber() will raise error for us if slot_index not in slot range - data = bytearray() - data.append(SlotNumber.to_fw(slot_index)) - data.append(0x01 if enable else 0x00) - return self.device.send_cmd_sync(DATA_CMD_SET_SLOT_ENABLE, 0X00, data) + data = struct.pack('!BBB', SlotNumber.to_fw(slot_index), sense_type, enabled) + return self.device.send_cmd_sync(DATA_CMD_SET_SLOT_ENABLE, data) @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) - def set_em410x_sim_id(self, id_bytes: bytearray): + def em410x_set_emu_id(self, id: bytes): """ Set the card number simulated by EM410x :param id_bytes: byte of the card number :return: """ - if len(id_bytes) != 5: + if len(id) != 5: raise ValueError("The id bytes length must equal 5") - return self.device.send_cmd_sync(DATA_CMD_SET_EM410X_EMU_ID, 0x00, id_bytes) + data = struct.pack('5s', id) + return self.device.send_cmd_sync(DATA_CMD_EM410X_SET_EMU_ID, data) - def get_em410x_sim_id(self): + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def em410x_get_emu_id(self): """ Get the simulated EM410x card id """ - return self.device.send_cmd_sync(DATA_CMD_GET_EM410X_EMU_ID, 0x00) + return self.device.send_cmd_sync(DATA_CMD_EM410X_GET_EMU_ID) @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) - def set_mf1_detection_enable(self, enable: bool): + def mf1_set_detection_enable(self, enabled: bool): """ Set whether to enable the detection of the current card slot :param enable: Whether to enable :return: """ - data = bytearray() - data.append(0x01 if enable else 0x00) - return self.device.send_cmd_sync(DATA_CMD_SET_MF1_DETECTION_ENABLE, 0x00, data) + data = struct.pack('!B', enabled) + return self.device.send_cmd_sync(DATA_CMD_MF1_SET_DETECTION_ENABLE, data) - def get_mf1_detection_count(self): + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def mf1_get_detection_count(self): """ Get the statistics of the current detection records :return: """ - return self.device.send_cmd_sync(DATA_CMD_GET_MF1_DETECTION_COUNT, 0x00) + resp = self.device.send_cmd_sync(DATA_CMD_MF1_GET_DETECTION_COUNT) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + resp.data, = struct.unpack('!I', resp.data) + return resp @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) - def get_mf1_detection_log(self, index: int): + def mf1_get_detection_log(self, index: int): """ Get detection logs from the specified index position :param index: start index :return: """ - data = bytearray() - data.extend(index.to_bytes(4, "big", signed=False)) - return self.device.send_cmd_sync(DATA_CMD_GET_MF1_DETECTION_RESULT, 0x00, data) + data = struct.pack('!I', index) + resp = self.device.send_cmd_sync(DATA_CMD_MF1_GET_DETECTION_LOG, data) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + # convert + result_list = [] + pos = 0 + while pos < len(resp.data): + block, bitfield, uid, nt, nr, ar = struct.unpack_from('!BB4s4s4s4s', resp.data, pos) + result_list.append({ + 'block': block, + 'type': ['A', 'B'][bitfield & 0x01], + 'is_nested': bool(bitfield & 0x02), + 'uid': uid.hex(), + 'nt': nt.hex(), + 'nr': nr.hex(), + 'ar': ar.hex() + }) + pos += struct.calcsize('!BB4s4s4s4s') + resp.data = result_list + return resp @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) - def set_mf1_block_data(self, block_start: int, block_data: bytearray): + def mf1_write_emu_block_data(self, block_start: int, block_data: bytes): """ Set the block data of the analog card of MF1 :param block_start: Start setting the location of block data, including this location @@ -622,61 +863,57 @@ def set_mf1_block_data(self, block_start: int, block_data: bytearray): can contain multiple block data, automatically from block_start increment :return: """ - data = bytearray() - data.append(block_start & 0xFF) - data.extend(block_data) - return self.device.send_cmd_sync(DATA_CMD_LOAD_MF1_EMU_BLOCK_DATA, 0x00, data) + data = struct.pack(f'!B{len(block_data)}s', block_start, block_data) + return self.device.send_cmd_sync(DATA_CMD_MF1_WRITE_EMU_BLOCK_DATA, data) - def get_mf1_block_data(self, block_start: int, block_count: int): + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def mf1_read_emu_block_data(self, block_start: int, block_count: int): """ Gets data for selected block range """ - return self.device.send_cmd_sync(DATA_CMD_READ_MF1_EMU_BLOCK_DATA, 0x00, bytearray([block_start, block_count])) + data = struct.pack('!BB', block_start, block_count) + return self.device.send_cmd_sync(DATA_CMD_MF1_READ_EMU_BLOCK_DATA, data) @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) - def set_mf1_anti_collision_res(self, sak: bytearray, atqa: bytearray, uid: bytearray): + def hf14a_set_anti_coll_data(self, uid: bytes, atqa: bytes, sak: bytes, ats: bytes = b''): """ - Set the anti-collision resource information of the MF1 analog card + Set anti-collision data of current HF slot (UID/SAK/ATQA/ATS) + :param uid: uid bytes + :param atqa: atqa bytes :param sak: sak bytes - :param atqa: atqa array - :param uid: card number array + :param ats: ats bytes (optional) :return: """ - data = bytearray() - data.extend(sak) - data.extend(atqa) - data.extend(uid) - return self.device.send_cmd_sync(DATA_CMD_SET_MF1_ANTI_COLLISION_RES, 0X00, data) + data = struct.pack(f'!B{len(uid)}s2s1sB{len(ats)}s', len(uid), uid, atqa, sak, len(ats), ats) + return self.device.send_cmd_sync(DATA_CMD_HF14A_SET_ANTI_COLL_DATA, data) @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) - def set_slot_tag_nick_name(self, slot: SlotNumber, sense_type: TagSenseType, name: bytes): + def set_slot_tag_nick(self, slot: SlotNumber, sense_type: TagSenseType, name: bytes): """ - Set the anti-collision resource information of the MF1 analog card + Set the nick name of the slot :param slot: Card slot number :param sense_type: field type :param name: Card slot nickname :return: """ # SlotNumber() will raise error for us if slot not in slot range - data = bytearray() - data.extend([SlotNumber.to_fw(slot), sense_type]) - data.extend(name) - return self.device.send_cmd_sync(DATA_CMD_SET_SLOT_TAG_NICK, 0x00, data) + data = struct.pack(f'!BB{len(name)}s', SlotNumber.to_fw(slot), sense_type, name) + return self.device.send_cmd_sync(DATA_CMD_SET_SLOT_TAG_NICK, data) @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) - def get_slot_tag_nick_name(self, slot: SlotNumber, sense_type: TagSenseType): + def get_slot_tag_nick(self, slot: SlotNumber, sense_type: TagSenseType): """ - Set the anti-collision resource information of the MF1 analog card + Get the nick name of the slot :param slot: Card slot number :param sense_type: field type :return: """ # SlotNumber() will raise error for us if slot not in slot range - data = bytearray() - data.extend([SlotNumber.to_fw(slot), sense_type]) - return self.device.send_cmd_sync(DATA_CMD_GET_SLOT_TAG_NICK, 0x00, data) + data = struct.pack('!BB', SlotNumber.to_fw(slot), sense_type) + return self.device.send_cmd_sync(DATA_CMD_GET_SLOT_TAG_NICK, data) - def get_mf1_emulator_settings(self): + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def mf1_get_emulator_config(self): """ Get array of Mifare Classic emulators settings: [0] - mf1_is_detection_enable (mfkey32) @@ -686,118 +923,166 @@ def get_mf1_emulator_settings(self): [4] - mf1_get_write_mode :return: """ - return self.device.send_cmd_sync(DATA_CMD_GET_MF1_EMULATOR_CONFIG, 0x00) + resp = self.device.send_cmd_sync(DATA_CMD_MF1_GET_EMULATOR_CONFIG) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + b1, b2, b3, b4, b5 = struct.unpack('!????B', resp.data) + resp.data = {'detection': b1, + 'gen1a_mode': b2, + 'gen2_mode': b3, + 'block_anti_coll_mode': b4, + 'write_mode': b5} + return resp - def set_mf1_gen1a_mode(self, enabled: bool): + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def mf1_set_gen1a_mode(self, enabled: bool): """ Set gen1a magic mode """ - return self.device.send_cmd_sync(DATA_CMD_SET_MF1_GEN1A_MODE, 0x00, bytearray([1 if enabled else 0])) + data = struct.pack('!B', enabled) + return self.device.send_cmd_sync(DATA_CMD_MF1_SET_GEN1A_MODE, data) - def set_mf1_gen2_mode(self, enabled: bool): + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def mf1_set_gen2_mode(self, enabled: bool): """ Set gen2 magic mode """ - return self.device.send_cmd_sync(DATA_CMD_SET_MF1_GEN2_MODE, 0x00, bytearray([1 if enabled else 0])) + data = struct.pack('!B', enabled) + return self.device.send_cmd_sync(DATA_CMD_MF1_SET_GEN2_MODE, data) - def set_mf1_block_anti_coll_mode(self, enabled: bool): + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def mf1_set_block_anti_coll_mode(self, enabled: bool): """ Set 0 block anti-collision data """ - return self.device.send_cmd_sync(DATA_CMD_SET_MF1_USE_FIRST_BLOCK_COLL, 0x00, bytearray([1 if enabled else 0])) + data = struct.pack('!B', enabled) + return self.device.send_cmd_sync(DATA_CMD_MF1_SET_BLOCK_ANTI_COLL_MODE, data) - def set_mf1_write_mode(self, mode: int): + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def mf1_set_write_mode(self, mode: int): """ Set write mode """ - return self.device.send_cmd_sync(DATA_CMD_SET_MF1_WRITE_MODE, 0x00, bytearray([mode])) + data = struct.pack('!B', mode) + return self.device.send_cmd_sync(DATA_CMD_MF1_SET_WRITE_MODE, data) - def update_slot_data_config(self): + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def slot_data_config_save(self): """ Update the configuration and data of the card slot to flash. :return: """ - return self.device.send_cmd_sync(DATA_CMD_SLOT_DATA_CONFIG_SAVE, 0x00) + return self.device.send_cmd_sync(DATA_CMD_SLOT_DATA_CONFIG_SAVE) - def enter_dfu_mode(self): + def enter_bootloader(self): """ Reboot into DFU mode (bootloader) :return: """ - return self.device.send_cmd_auto(DATA_CMD_ENTER_BOOTLOADER, 0x00, close=True) + self.device.send_cmd_auto(DATA_CMD_ENTER_BOOTLOADER, close=True) - def get_settings_animation(self): + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def get_animation_mode(self): """ Get animation mode value """ - return self.device.send_cmd_sync(DATA_CMD_GET_ANIMATION_MODE, 0x00) + resp = self.device.send_cmd_sync(DATA_CMD_GET_ANIMATION_MODE) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + resp.data = resp.data[0] + return resp + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) def get_enabled_slots(self): """ Get enabled slots """ - return self.device.send_cmd_sync(DATA_CMD_GET_ENABLED_SLOTS, 0x00) + resp = self.device.send_cmd_sync(DATA_CMD_GET_ENABLED_SLOTS) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + resp.data = [{'hf': hf, 'lf': lf} for hf, lf in struct.iter_unpack('!BB', resp.data)] + return resp - def set_settings_animation(self, value: int): + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def set_animation_mode(self, value: int): """ Set animation mode value """ - return self.device.send_cmd_sync(DATA_CMD_SET_ANIMATION_MODE, 0x00, bytearray([value])) + data = struct.pack('!B', value) + return self.device.send_cmd_sync(DATA_CMD_SET_ANIMATION_MODE, data) + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) def reset_settings(self): """ Reset settings stored in flash memory """ - return self.device.send_cmd_sync(DATA_CMD_RESET_SETTINGS, 0x00) + resp = self.device.send_cmd_sync(DATA_CMD_RESET_SETTINGS) + resp.data = resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS + return resp - def store_settings(self): + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def save_settings(self): """ Store settings to flash memory """ - return self.device.send_cmd_sync(DATA_CMD_SAVE_SETTINGS, 0x00) + resp = self.device.send_cmd_sync(DATA_CMD_SAVE_SETTINGS) + resp.data = resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS + return resp - def factory_reset(self): + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def wipe_fds(self): """ Reset to factory settings """ - ret = self.device.send_cmd_sync(DATA_CMD_WIPE_FDS, 0x00) + resp = self.device.send_cmd_sync(DATA_CMD_WIPE_FDS) + resp.data = resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS self.device.close() - return ret + return resp @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) - def battery_information(self): + def get_battery_info(self): """ Get battery info """ - return self.device.send_cmd_sync(DATA_CMD_GET_BATTERY_INFO, 0x00) + resp = self.device.send_cmd_sync(DATA_CMD_GET_BATTERY_INFO) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + resp.data = struct.unpack('!HB', resp.data) + return resp @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) - def get_button_press_fun(self, button: ButtonType): + def get_button_press_config(self, button: ButtonType): """ Get config of button press function """ - return self.device.send_cmd_sync(DATA_CMD_GET_BUTTON_PRESS_CONFIG, 0x00, bytearray([button])) + data = struct.pack('!B', button) + resp = self.device.send_cmd_sync(DATA_CMD_GET_BUTTON_PRESS_CONFIG, data) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + resp.data = resp.data[0] + return resp @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) - def set_button_press_fun(self, button: ButtonType, function: ButtonPressFunction): + def set_button_press_config(self, button: ButtonType, function: ButtonPressFunction): """ Set config of button press function """ - return self.device.send_cmd_sync(DATA_CMD_SET_BUTTON_PRESS_CONFIG, 0x00, bytearray([button, function])) + data = struct.pack('!BB', button, function) + return self.device.send_cmd_sync(DATA_CMD_SET_BUTTON_PRESS_CONFIG, data) @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) - def get_long_button_press_fun(self, button: ButtonType): + def get_long_button_press_config(self, button: ButtonType): """ - Get config of button press function + Get config of long button press function """ - return self.device.send_cmd_sync(DATA_CMD_GET_LONG_BUTTON_PRESS_CONFIG, 0x00, bytearray([button])) + data = struct.pack('!B', button) + resp = self.device.send_cmd_sync(DATA_CMD_GET_LONG_BUTTON_PRESS_CONFIG, data) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + resp.data = resp.data[0] + return resp @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) - def set_long_button_press_fun(self, button: ButtonType, function: ButtonPressFunction): + def set_long_button_press_config(self, button: ButtonType, function: ButtonPressFunction): """ - Set config of button press function + Set config of long button press function """ - return self.device.send_cmd_sync(DATA_CMD_SET_LONG_BUTTON_PRESS_CONFIG, 0x00, bytearray([button, function])) + data = struct.pack('!BB', button, function) + return self.device.send_cmd_sync(DATA_CMD_SET_LONG_BUTTON_PRESS_CONFIG, data) @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) def set_ble_connect_key(self, key: str): @@ -810,44 +1095,53 @@ def set_ble_connect_key(self, key: str): if len(data_bytes) != 6: raise ValueError("The ble connect key length must be 6") - return self.device.send_cmd_sync(DATA_CMD_SET_BLE_CONNECT_KEY_CONFIG, 0x00, data_bytes) + data = struct.pack('6s', data_bytes) + return self.device.send_cmd_sync(DATA_CMD_SET_BLE_PAIRING_KEY, data) - def get_ble_connect_key(self): + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def get_ble_pairing_key(self): """ Get config of ble connect key """ - return self.device.send_cmd_sync(DATA_CMD_GET_BLE_CONNECT_KEY_CONFIG, 0x00, None) + return self.device.send_cmd_sync(DATA_CMD_GET_BLE_PAIRING_KEY) + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) def delete_ble_all_bonds(self): """ From peer manager delete all bonds. """ - return self.device.send_cmd_sync(DATA_CMD_DELETE_ALL_BLE_BONDS, 0x00, None) + return self.device.send_cmd_sync(DATA_CMD_DELETE_ALL_BLE_BONDS) + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) def get_device_capabilities(self): """ - Get (and set) commands that client understands + Get list of commands that client understands """ - - commands = [] - try: - ret = self.device.send_cmd_sync(DATA_CMD_GET_DEVICE_CAPABILITIES, 0x00) - self.device.commands = struct.unpack(f"{len(ret.data) // 2}H", ret.data) - except: - print("Chameleon doesn't understand get capabilities command. Please update firmware") - - return commands + resp = self.device.send_cmd_sync(DATA_CMD_GET_DEVICE_CAPABILITIES) + except chameleon_com.CMDInvalidException: + print("Chameleon does not understand get_device_capabilities command. Please update firmware") + return chameleon_com.Response(cmd=DATA_CMD_GET_DEVICE_CAPABILITIES, + status=chameleon_status.Device.STATUS_NOT_IMPLEMENTED) + else: + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + resp.data = [x[0] for x in struct.iter_unpack('!H', resp.data)] + return resp - def get_device_type(self): + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def get_device_model(self): """ - Get device type + Get device model 0 - Chameleon Ultra 1 - Chameleon Lite """ - return self.device.send_cmd_sync(DATA_CMD_GET_DEVICE, 0x00).data[0] + resp = self.device.send_cmd_sync(DATA_CMD_GET_DEVICE_MODEL) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + resp.data = resp.data[0] + return resp + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) def get_device_settings(self): """ Get all possible settings @@ -858,48 +1152,128 @@ def get_device_settings(self): settings[3] = settings_get_button_press_config('B'); // short B button press mode settings[4] = settings_get_long_button_press_config('A'); // long A button press mode settings[5] = settings_get_long_button_press_config('B'); // long B button press mode - settings[6] = settings_get_ble_pairing_enable(); // is device require pairing - settings[7:13] = settings_get_ble_connect_key(); // BLE connection key - """ - data = self.device.send_cmd_sync(DATA_CMD_GET_SETTINGS, 0x00).data - if data[0] != CURRENT_VERSION_SETTINGS: - raise ValueError("Settings version in app doesn't match Chameleon response. Upgrade client or Chameleon " - "firmware") - return data + settings[6] = settings_get_ble_pairing_enable(); // does device require pairing + settings[7:13] = settings_get_ble_pairing_key(); // BLE pairing key + """ + resp = self.device.send_cmd_sync(DATA_CMD_GET_DEVICE_SETTINGS) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + if resp.data[0] > CURRENT_VERSION_SETTINGS: + raise ValueError("Settings version in app older than Chameleon. " + "Please upgrade client") + if resp.data[0] < CURRENT_VERSION_SETTINGS: + raise ValueError("Settings version in app newer than Chameleon. " + "Please upgrade Chameleon firmware") + settings_version, animation_mode, btn_press_A, btn_press_B, btn_long_press_A, btn_long_press_B, ble_pairing_enable, ble_pairing_key = struct.unpack( + '!BBBBBBB6s', resp.data) + resp.data = {'settings_version': settings_version, + 'animation_mode': animation_mode, + 'btn_press_A': btn_press_A, + 'btn_press_B': btn_press_B, + 'btn_long_press_A': btn_long_press_A, + 'btn_long_press_B': btn_long_press_B, + 'ble_pairing_enable': ble_pairing_enable, + 'ble_pairing_key': ble_pairing_key} + return resp - def get_mf1_anti_coll_data(self): + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def hf14a_get_anti_coll_data(self): """ - Get data from current slot (UID/SAK/ATQA) + Get anti-collision data from current HF slot (UID/SAK/ATQA/ATS) :return: """ - return self.device.send_cmd_sync(DATA_CMD_GET_MF1_ANTI_COLL_DATA, 0x00) + resp = self.device.send_cmd_sync(DATA_CMD_HF14A_GET_ANTI_COLL_DATA) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS and len(resp.data) > 0: + # uidlen[1]|uid[uidlen]|atqa[2]|sak[1]|atslen[1]|ats[atslen] + offset = 0 + uidlen, = struct.unpack_from('!B', resp.data, offset) + offset += struct.calcsize('!B') + uid, atqa, sak, atslen = struct.unpack_from(f'!{uidlen}s2s1sB', resp.data, offset) + offset += struct.calcsize(f'!{uidlen}s2s1sB') + ats, = struct.unpack_from(f'!{atslen}s', resp.data, offset) + offset += struct.calcsize(f'!{atslen}s') + resp.data = {'uid': uid, 'atqa': atqa, 'sak': sak, 'ats': ats} + return resp - def get_ble_pairing_enable(self) -> bool: + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def get_ble_pairing_enable(self): """ Is ble pairing enable? :return: True if pairing is enable, False if pairing disabled """ - resp = self.device.send_cmd_sync(DATA_CMD_GET_BLE_PAIRING_ENABLE, 0x00) - return resp.data[0] == 1 - + resp = self.device.send_cmd_sync(DATA_CMD_GET_BLE_PAIRING_ENABLE) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + resp.data, = struct.unpack('!?', resp.data) + return resp + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) - def set_ble_pairing_enable(self, enable: bool): - return self.device.send_cmd_sync(DATA_CMD_SET_BLE_PAIRING_ENABLE, 0x00, 1 if enable else 0) + def set_ble_pairing_enable(self, enabled: bool): + data = struct.pack('!B', enabled) + return self.device.send_cmd_sync(DATA_CMD_SET_BLE_PAIRING_ENABLE, data) -if __name__ == '__main__': +def test_fn(): # connect to chameleon dev = chameleon_com.ChameleonCom() - dev.open("com19") + try: + dev.open('com19') + except chameleon_com.OpenFailException: + dev.open('/dev/ttyACM0') + cml = ChameleonCMD(dev) - ver = cml.get_firmware_version() - print(f"Firmware number of application: {ver}") + ver = cml.get_app_version() + print(f"Firmware number of application: {ver[0]}.{ver[1]}") chip = cml.get_device_chip_id() print(f"Device chip id: {chip}") + # change to reader mode + cml.set_device_reader_mode() + + options = { + 'activate_rf_field': 1, + 'wait_response': 1, + 'append_crc': 0, + 'auto_select': 0, + 'keep_rf_field': 1, + 'check_response_crc': 0, + } + + try: + # unlock 1 + resp = cml.hf14a_raw(options=options, resp_timeout_ms=1000, data=[0x40], bitlen=7) + + if resp[0] == 0x0a: + print("Gen1A unlock 1 success") + # unlock 2 + resp = cml.hf14a_raw(options=options, resp_timeout_ms=1000, data=[0x43]) + if resp[0] == 0x0a: + print("Gen1A unlock 2 success") + print("Start dump gen1a memory...") + # Transfer with crc + options['append_crc'] = 1 + options['check_response_crc'] = 1 + block = 0 + while block < 64: + # Tag read block cmd + cmd_read_gen1a_block = [0x30, block] + if block == 63: + options['keep_rf_field'] = 0 + resp = cml.hf14a_raw(options=options, resp_timeout_ms=100, data=cmd_read_gen1a_block) + + print(f"Block {block} : {resp.hex()}") + block += 1 + + else: + print("Gen1A unlock 2 fail") + else: + print("Gen1A unlock 1 fail") + except: + options['keep_rf_field'] = 0 + options['wait_response'] = 0 + cml.hf14a_raw(options=options) + # disconnect dev.close() - # never exit - while True: - pass + +if __name__ == '__main__': + test_fn() diff --git a/software/script/chameleon_com.py b/software/script/chameleon_com.py index c309fb1f..eeee8d0b 100644 --- a/software/script/chameleon_com.py +++ b/software/script/chameleon_com.py @@ -29,7 +29,7 @@ class Response: Chameleon Response Data """ - def __init__(self, cmd, status, data): + def __init__(self, cmd, status, data=b''): self.cmd = cmd self.status = status self.data: bytearray = data @@ -153,37 +153,35 @@ def thread_data_receive(self): if len(data_bytes) > 0: data_byte = data_bytes[0] data_buffer.append(data_byte) - if data_position < 2: # start of frame + if data_position < struct.calcsize('!BB'): # start of frame + lrc1 if data_position == 0: if data_buffer[data_position] != self.data_frame_sof: print("Data frame no sof byte.") data_position = 0 data_buffer.clear() continue - if data_position == 1: - if data_buffer[data_position] != self.lrc_calc(data_buffer[0:1]): + if data_position == struct.calcsize('!B'): + if data_buffer[data_position] != self.lrc_calc(data_buffer[:data_position]): data_position = 0 data_buffer.clear() print("Data frame sof lrc error.") continue - elif data_position == 8: # frame head lrc - if data_buffer[data_position] != self.lrc_calc(data_buffer[0:8]): + elif data_position == struct.calcsize('!BBHHH'): # frame head lrc + if data_buffer[data_position] != self.lrc_calc(data_buffer[:data_position]): data_position = 0 data_buffer.clear() print("Data frame head lrc error.") continue # frame head complete, cache info - data_cmd = struct.unpack(">H", data_buffer[2:4])[0] - data_status = struct.unpack(">H", data_buffer[4:6])[0] - data_length = struct.unpack(">H", data_buffer[6:8])[0] + _, _, data_cmd, data_status, data_length = struct.unpack("!BBHHH", data_buffer[:data_position]) if data_length > self.data_max_length: data_position = 0 data_buffer.clear() - print("Data frame data length too than of max.") + print("Data frame data length larger than max.") continue - elif data_position > 8: # // frame data - if data_position == (8 + data_length + 1): - if data_buffer[data_position] == self.lrc_calc(data_buffer[0:-1]): + elif data_position > struct.calcsize('!BBHHH'): # // frame data + if data_position == (struct.calcsize(f'!BBHHHB{data_length}s')): + if data_buffer[data_position] == self.lrc_calc(data_buffer[:data_position]): # ok, lrc for data is correct. # and we are receive completed # print(f"Buffer data = {data_buffer.hex()}") @@ -193,7 +191,8 @@ def thread_data_receive(self): fn_call = self.wait_response_map[data_cmd]['callback'] else: fn_call = None - data_response = data_buffer[9: 9 + data_length] + data_response = data_buffer[struct.calcsize('!BBHHHB'): + struct.calcsize(f'!BBHHHB{data_length}s')] if callable(fn_call): # delete wait task from map del self.wait_response_map[data_cmd] @@ -204,7 +203,7 @@ def thread_data_receive(self): else: print(f"No task wait process: ${data_cmd}") else: - print("Data frame finally lrc error.") + print("Data frame global lrc error.") data_position = 0 data_buffer.clear() continue @@ -265,36 +264,32 @@ def thread_check_timeout(self): self.wait_response_map[task_cmd]['is_timeout'] = True time.sleep(0.001) - def make_data_frame_bytes(self, cmd: int, status: int, data: bytearray = None) -> bytearray: + def make_data_frame_bytes(self, cmd: int, data: bytearray = None, status: int = 0) -> bytearray: """ Make data frame :return: frame """ - frame = bytearray() - # sof and sof lrc byte - frame.append(self.data_frame_sof) - frame.append(self.lrc_calc(frame[0:1])) - # head info - frame.extend(struct.pack('>H', cmd)) - frame.extend(struct.pack('>H', status)) - frame.extend(struct.pack('>H', len(data) if data is not None else 0)) - frame.append(self.lrc_calc(frame[2:8])) - # data - if data is not None: - frame.extend(data) - # frame lrc - frame.append(self.lrc_calc(frame)) + if data is None: + data = b'' + frame = bytearray(struct.pack(f'!BBHHHB{len(data)}sB', + self.data_frame_sof, 0x00, cmd, status, len(data), 0x00, data, 0x00)) + # lrc1 + frame[struct.calcsize('!B')] = self.lrc_calc(frame[:struct.calcsize('!B')]) + # lrc2 + frame[struct.calcsize('!BBHHH')] = self.lrc_calc(frame[:struct.calcsize('!BBHHH')]) + # lrc3 + frame[struct.calcsize(f'!BBHHHB{len(data)}s')] = self.lrc_calc(frame[:struct.calcsize(f'!BBHHHB{len(data)}s')]) return frame - def send_cmd_auto(self, cmd: int, status: int, data: bytearray = None, callback=None, timeout: int = 3, + def send_cmd_auto(self, cmd: int, data: bytearray = None, status: int = 0, callback=None, timeout: int = 3, close: bool = False): """ Send cmd to device - :param timeout: wait response timeout :param cmd: cmd - :param status: status(optional) + :param data: bytes data (optional) + :param status: status (optional) :param callback: call on response - :param data: bytes data + :param timeout: wait response timeout :param close: close connection after executing :return: """ @@ -303,20 +298,20 @@ def send_cmd_auto(self, cmd: int, status: int, data: bytearray = None, callback= if cmd in self.wait_response_map: del self.wait_response_map[cmd] # make data frame - data_frame = self.make_data_frame_bytes(cmd, status, data) + data_frame = self.make_data_frame_bytes(cmd, data, status) task = {'cmd': cmd, 'frame': data_frame, 'timeout': timeout, 'close': close} if callable(callback): task['callback'] = callback self.send_data_queue.put(task) return self - def send_cmd_sync(self, cmd: int, status: int, data: bytearray or bytes or list or int = None, + def send_cmd_sync(self, cmd: int, data: bytearray or bytes or list or int = None, status: int = 0, timeout: int = 3) -> Response: """ Send cmd to device, and block receive data. :param cmd: cmd - :param status: status(optional) - :param data: bytes data + :param data: bytes data (optional) + :param status: status (optional) :param timeout: wait response timeout :return: response data """ @@ -327,9 +322,8 @@ def send_cmd_sync(self, cmd: int, status: int, data: bytearray or bytes or list if cmd not in self.commands: raise CMDInvalidException(f"This device doesn't declare that it can support this command: {cmd}.\nMake " f"sure firmware is up to date and matches client") - # return Response(cmd=cmd, status=0, data=b"\0" * 32) # forge fake response to not break app # first to send cmd, no callback mode(sync) - self.send_cmd_auto(cmd, status, data, None, timeout) + self.send_cmd_auto(cmd, data, status, None, timeout) # wait cmd start process while cmd not in self.wait_response_map: time.sleep(0.01) @@ -347,6 +341,11 @@ def send_cmd_sync(self, cmd: int, status: int, data: bytearray or bytes or list if __name__ == '__main__': - cml = ChameleonCom().open("com19") - resp = cml.send_cmd_sync(0x03E9, 0xBEEF, bytearray([0x01, 0x02])) - print(resp) + try: + cml = ChameleonCom().open('com19') + except OpenFailException: + cml = ChameleonCom().open('/dev/ttyACM0') + resp = cml.send_cmd_sync(0x03E8, None, 0) + print(resp.status) + print(resp.data) + cml.close() diff --git a/software/script/chameleon_cstruct.py b/software/script/chameleon_cstruct.py deleted file mode 100644 index 14edc5fe..00000000 --- a/software/script/chameleon_cstruct.py +++ /dev/null @@ -1,113 +0,0 @@ -""" - From bytes to c struct parser(Chameleon data response) -""" - - -def bytes_to_u32(byte_array): - """ - bytes array to u32 int - :param byte_array: - :return: - """ - return int.from_bytes(byte_array, byteorder='big', signed=False) - - -def parse_14a_scan_tag_result(data: bytearray): - """ - From bytes parse tag info - :param data: - :return: - """ - return { - 'uid_size': data[10], - 'uid_hex': data[0:data[10]].hex(), - 'sak_hex': hex(data[12]).lstrip('0x').rjust(2, '0'), - 'atqa_hex': data[13:15].hex().upper() - } - - -def parse_nt_distance_detect_result(data: bytearray): - """ - From bytes parse nt distance - :param data: data - :return: - """ - return {'uid': bytes_to_u32(data[0:4]), 'dist': bytes_to_u32(data[4:8])} - - -def parse_nested_nt_acquire_group(data: bytearray): - """ - From bytes parse nested param - :param data: data - :return: - """ - group = [] - if len(data) % 9 != 0: - raise ValueError( - "Nt data length error, except: { nt(4byte), nt_enc(4byte), par(1byte) } * N") - i = 0 - while i < len(data): - group.append({ - 'nt': bytes_to_u32(data[i: i + 4]), - 'nt_enc': bytes_to_u32(data[i + 4: i + 8]), - 'par': data[i + 8] - }) - i += 9 - return group - - -def parse_darkside_acquire_result(data: bytearray): - """ - From bytes parse darkside param - :param data: data - :return: - """ - return { - 'uid': bytes_to_u32(data[0: 4]), - 'nt1': bytes_to_u32(data[4: 8]), - 'par': bytes_to_u32(data[8: 16]), - 'ks1': bytes_to_u32(data[16: 24]), - 'nr': bytes_to_u32(data[24: 28]), - 'ar': bytes_to_u32(data[28: 32]) - } - - -def parse_mf1_detection_result(data: bytearray): - """ - From bytes parse detection param - :param data: data - :return: - """ - # convert - result_list = [] - pos = 0 - while pos < len(data): - result_list.append({ - 'block': data[0 + pos], - 'type': 0x60 + (data[1 + pos] & 0x01), - 'is_nested': True if data[1 + pos] >> 1 & 0x01 == 0x01 else False, - 'uid': data[2 + pos: 6 + pos].hex(), - 'nt': data[6 + pos: 10 + pos].hex(), - 'nr': data[10 + pos: 14 + pos].hex(), - 'ar': data[14 + pos: 18 + pos].hex() - }) - pos += 18 - - # classify - result_map = {} - for item in result_list: - uid = item['uid'] - if uid not in result_map: - result_map[uid] = {} - - block = item['block'] - if block not in result_map[uid]: - result_map[uid][block] = {} - - type_chr = 'A' if item['type'] == 0x60 else 'B' - if type_chr not in result_map[uid][block]: - result_map[uid][block][type_chr] = [] - - result_map[uid][block][type_chr].append(item) - - return result_map diff --git a/software/script/chameleon_status.py b/software/script/chameleon_status.py index c3a9180b..da467f50 100644 --- a/software/script/chameleon_status.py +++ b/software/script/chameleon_status.py @@ -12,6 +12,14 @@ def __contains__(self, item): return True return False + def __getitem__(self, item): + for field in self.__dict__: + val = self.__dict__[field] + if isinstance(val, int): + if val == item: + return field + return False + class Device(metaclass=MetaDevice): HF_TAG_OK = 0x00 # IC card operation is successful @@ -22,20 +30,7 @@ class Device(metaclass=MetaDevice): HF_ERR_BCC = 0x05 # IC card BCC error MF_ERR_AUTH = 0x06 # MF card verification failed HF_ERR_PARITY = 0x07 # IC card parity error - - # Darkside, the random number cannot be fixed, this situation may appear on the UID card - DARKSIDE_CANT_FIXED_NT = 0x20 - # Darkside, the direct verification is successful, maybe the key is empty - DARKSIDE_LUCK_AUTH_OK = 0x21 - # Darkside, the card doesn't respond to nack, probably a card that fixes the nack logic bug - DARKSIDE_NACK_NO_SEND = 0x22 - # Darkside, there is a card switching during the running of darkside, - # maybe there is a signal problem, or the two cards really switched quickly - DARKSIDE_TAG_CHANGED = 0x23 - # Nested, it is detected that the random number of the card response is fixed - NESTED_TAG_IS_STATIC = 0x24 - # Nested, detected nonce for card response is unpredictable - NESTED_TAG_IS_HARD = 0x25 + HF_ERR_ATS = 0x08 # ATS should be present but card NAKed, or ATS too large # Some operations with low frequency cards succeeded! LF_TAG_OK = 0x40 @@ -63,13 +58,7 @@ class Device(metaclass=MetaDevice): Device.HF_ERR_BCC: "HF tag uid bcc error", Device.MF_ERR_AUTH: "HF tag auth fail", Device.HF_ERR_PARITY: "HF tag data parity error", - - Device.DARKSIDE_CANT_FIXED_NT: "Darkside Can't select a nt(PRNG is unpredictable)", - Device.DARKSIDE_LUCK_AUTH_OK: "Darkside try to recover a default key", - Device.DARKSIDE_NACK_NO_SEND: "Darkside can't make tag response nack(enc)", - Device.DARKSIDE_TAG_CHANGED: "Darkside running, can't change tag", - Device.NESTED_TAG_IS_STATIC: "StaticNested tag, not weak nested", - Device.NESTED_TAG_IS_HARD: "HardNested tag, not weak nested", + Device.HF_ERR_ATS: "HF tag was supposed to send ATS but didn't", Device.LF_TAG_OK: "LF tag operation succeeded", Device.EM410X_TAG_NO_FOUND: "EM410x tag no found", diff --git a/software/script/chameleon_utils.py b/software/script/chameleon_utils.py index d2e8e85c..089992fe 100644 --- a/software/script/chameleon_utils.py +++ b/software/script/chameleon_utils.py @@ -54,7 +54,6 @@ def decorator(func): @wraps(func) def error_throwing_func(*args, **kwargs): ret = func(*args, **kwargs) - if ret.status not in accepted_responses: if ret.status in chameleon_status.Device and ret.status in chameleon_status.message: raise UnexpectedResponseError( @@ -63,7 +62,7 @@ def error_throwing_func(*args, **kwargs): raise UnexpectedResponseError( f"Unexpected response and unknown status {ret.status}") - return ret + return ret.data return error_throwing_func diff --git a/software/src/CMakeLists.txt b/software/src/CMakeLists.txt index 5444f767..7c4d0665 100644 --- a/software/src/CMakeLists.txt +++ b/software/src/CMakeLists.txt @@ -6,10 +6,12 @@ set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../bin) set(SRC_DIR ./) set(COMMON_FILES + ${SRC_DIR}/common.c ${SRC_DIR}/crapto1.c ${SRC_DIR}/crypto1.c ${SRC_DIR}/bucketsort.c ${SRC_DIR}/mfkey.c + ${SRC_DIR}/nested_util.c ${SRC_DIR}/parity.c) include_directories( @@ -19,6 +21,7 @@ include_directories( # tools add_executable(nested ${COMMON_FILES} nested.c) +add_executable(staticnested ${COMMON_FILES} staticnested.c) add_executable(darkside ${COMMON_FILES} darkside.c) add_executable(mfkey32 ${COMMON_FILES} mfkey32.c) add_executable(mfkey32v2 ${COMMON_FILES} mfkey32v2.c) diff --git a/software/src/common.c b/software/src/common.c new file mode 100644 index 00000000..0b9cc88d --- /dev/null +++ b/software/src/common.c @@ -0,0 +1,20 @@ +#include + + +uint64_t atoui(const char *str) { + + uint64_t result = 0; + for (int i = 0; str[i] != '\0'; ++i) { + if (str[i] >= '0' && str[i] <= '9') { + result = result * 10 + str[i] - '0'; + } + } + return result; +} + +void num_to_bytes(uint64_t n, uint32_t len, uint8_t *dest) { + while (len--) { + dest[len] = (uint8_t)n; + n >>= 8; + } +} diff --git a/software/src/common.h b/software/src/common.h new file mode 100644 index 00000000..f66f87ed --- /dev/null +++ b/software/src/common.h @@ -0,0 +1,7 @@ +#ifndef COMMON_H__ +#define COMMON_H__ + +uint64_t atoui(const char *str); +void num_to_bytes(uint64_t n, uint32_t len, uint8_t *dest); + +#endif diff --git a/software/src/darkside.c b/software/src/darkside.c index 99105b4d..5bc72b92 100644 --- a/software/src/darkside.c +++ b/software/src/darkside.c @@ -7,6 +7,7 @@ #include "parity.h" #include "crapto1.h" #include "mfkey.h" +#include "common.h" typedef struct { uint32_t nt; @@ -17,25 +18,6 @@ typedef struct { uint64_t ks_list; } DarksideParam; -// Convert string to U32 type -uint64_t atoui(const char *str) { - - uint64_t result = 0; - for (int i = 0; str[i] != '\0'; ++i) { - if (str[i] >= '0' && str[i] <= '9') { - result = result * 10 + str[i] - '0'; - } - } - return result; -} - -void num_to_bytes(uint64_t n, uint32_t len, uint8_t *dest) { - while (len--) { - dest[len] = (uint8_t)n; - n >>= 8; - } -} - int main(int argc, char *argv[]) { if (((argc - 2) % 5) != 0) { diff --git a/software/src/nested.c b/software/src/nested.c index 2e8be0a1..fde471bb 100644 --- a/software/src/nested.c +++ b/software/src/nested.c @@ -1,214 +1,9 @@ #include #include #include -#include #include -#include -#include "crapto1.h" -#include "parity.h" - -#if WIN32 -#include "windows.h" -#else -#include "unistd.h" -#endif - - -#define MEM_CHUNK 10000 -#define TRY_KEYS 50 - - -typedef struct { - uint64_t key; - int count; -} countKeys; - -typedef struct { - uint32_t ntp; - uint32_t ks1; -} NtpKs1; - -typedef struct { - NtpKs1 *pNK; - uint32_t authuid; - - uint64_t *keys; - uint32_t keyCount; - - uint32_t startPos; - uint32_t endPos; -} RecPar; - - -int compar_int(const void *a, const void *b) { - return (*(uint64_t *)b - * (uint64_t *)a); -} - -// Compare countKeys structure -int compar_special_int(const void *a, const void *b) { - return (((countKeys *)b)->count - ((countKeys *)a)->count); -} - -// keys qsort and unique. -countKeys *uniqsort(uint64_t *possibleKeys, uint32_t size) { - unsigned int i, j = 0; - int count = 0; - countKeys *our_counts; - - qsort(possibleKeys, size, sizeof(uint64_t), compar_int); - - our_counts = calloc(size, sizeof(countKeys)); - if (our_counts == NULL) { - printf("Memory allocation error for our_counts"); - exit(EXIT_FAILURE); - } - - for (i = 0; i < size; i++) { - if (possibleKeys[i + 1] == possibleKeys[i]) { - count++; - } else { - our_counts[j].key = possibleKeys[i]; - our_counts[j].count = count; - j++; - count = 0; - } - } - qsort(our_counts, j, sizeof(countKeys), compar_special_int); - return (our_counts); -} - -uint32_t atoui(const char *str) { - - uint32_t result = 0; - for (int i = 0; str[i] != '\0'; ++i) { - if (str[i] >= '0' && str[i] <= '9') { - result = result * 10 + str[i] - '0'; - } - } - return result; -} - -// nested decrypt -static void nested_revover(RecPar *rp) { - struct Crypto1State *revstate, * revstate_start = NULL; - uint64_t lfsr = 0; - uint32_t i, kcount = 0; - - rp->keyCount = 0; - rp->keys = NULL; - - for (i = rp->startPos; i < rp->endPos; i++) { - uint32_t nt_probe = rp->pNK[i].ntp; - uint32_t ks1 = rp->pNK[i].ks1; - // And finally recover the first 32 bits of the key - revstate = lfsr_recovery32(ks1, nt_probe ^ rp->authuid); - if (revstate_start == NULL) { - revstate_start = revstate; - } - while ((revstate->odd != 0x0) || (revstate->even != 0x0)) { - lfsr_rollback_word(revstate, nt_probe ^ rp->authuid, 0); - crypto1_get_lfsr(revstate, &lfsr); - // Allocate a new space for keys - if (((kcount % MEM_CHUNK) == 0) || (kcount >= rp->keyCount)) { - rp->keyCount += MEM_CHUNK; - // printf("New chunk by %d, sizeof %lu\n", kcount, key_count * sizeof(uint64_t)); - void *tmp = realloc(rp->keys, rp->keyCount * sizeof(uint64_t)); - if (tmp == NULL) { - printf("Memory allocation error for pk->possibleKeys"); - // exit(EXIT_FAILURE); - rp->keyCount = 0; - return; - } - rp->keys = (uint64_t *)tmp; - } - rp->keys[kcount] = lfsr; - kcount++; - revstate++; - } - free(revstate_start); - revstate_start = NULL; - } - // Truncate - if (kcount != 0) { - rp->keyCount = --kcount; - void *tmp = (uint64_t *)realloc(rp->keys, rp->keyCount * sizeof(uint64_t)); - if (tmp == NULL) { - printf("Memory allocation error for pk->possibleKeys"); - // exit(EXIT_FAILURE); - rp->keyCount = 0; - return; - } - rp->keys = tmp; - return; - } - rp->keyCount = 0; - return; -} - -uint64_t *nested(NtpKs1 *pNK, uint32_t sizePNK, uint32_t authuid, uint32_t *keyCount) { - *keyCount = 0; - uint32_t i; - - RecPar *pRPs = malloc(sizeof(RecPar)); - if (pRPs == NULL) { - return NULL; - } - - pRPs->pNK = pNK; - pRPs->authuid = authuid; - pRPs->startPos = 0; - pRPs->endPos = sizePNK; - - // start recover - nested_revover(pRPs); - *keyCount = pRPs->keyCount; - - uint64_t *keys = NULL; - if (*keyCount != 0) { - keys = malloc(*keyCount * sizeof(uint64_t)); - if (keys != NULL) { - memcpy(keys, pRPs->keys, pRPs->keyCount * sizeof(uint64_t)); - free(pRPs->keys); - } - } - free(pRPs); - - countKeys *ck = uniqsort(keys, *keyCount); - free(keys); - keys = (uint64_t *)NULL; - *keyCount = 0; - - if (ck != NULL) { - for (i = 0; i < TRY_KEYS; i++) { - // We don't known this key, try to break it - // This key can be found here two or more times - if (ck[i].count > 0) { - *keyCount += 1; - void *tmp = realloc(keys, sizeof(uint64_t) * (*keyCount)); - if (tmp != NULL) { - keys = tmp; - keys[*keyCount - 1] = ck[i].key; - } else { - printf("Cannot allocate memory for keys on merge."); - free(keys); - break; - } - } - } - } else { - printf("Cannot allocate memory for ck on uniqsort."); - } - return keys; -} - -// Return 1 if the nonce is invalid else return 0 -static uint8_t valid_nonce(uint32_t Nt, uint32_t NtEnc, uint32_t Ks1, uint8_t *parity) { - return ( - (oddparity8((Nt >> 24) & 0xFF) == ((parity[0]) ^ oddparity8((NtEnc >> 24) & 0xFF) ^ BIT(Ks1, 16))) && \ - (oddparity8((Nt >> 16) & 0xFF) == ((parity[1]) ^ oddparity8((NtEnc >> 16) & 0xFF) ^ BIT(Ks1, 8))) && \ - (oddparity8((Nt >> 8) & 0xFF) == ((parity[2]) ^ oddparity8((NtEnc >> 8) & 0xFF) ^ BIT(Ks1, 0))) - ) ? 1 : 0; -} +#include "common.h" +#include "nested_util.h" int main(int argc, char *const argv[]) { NtpKs1 *pNK = NULL; diff --git a/software/src/nested_util.c b/software/src/nested_util.c new file mode 100644 index 00000000..801a2799 --- /dev/null +++ b/software/src/nested_util.c @@ -0,0 +1,196 @@ +#include +#include +#include +#include +#include +#include +#include "parity.h" + +#if WIN32 +#include "windows.h" +#else +#include "unistd.h" +#endif + +#include "nested_util.h" + + +#define MEM_CHUNK 10000 +#define TRY_KEYS 50 + + +typedef struct { + uint64_t key; + int count; +} countKeys; + +typedef struct { + NtpKs1 *pNK; + uint32_t authuid; + + uint64_t *keys; + uint32_t keyCount; + + uint32_t startPos; + uint32_t endPos; +} RecPar; + + +int compar_int(const void *a, const void *b) { + return (*(uint64_t *)b - * (uint64_t *)a); +} + +// Compare countKeys structure +int compar_special_int(const void *a, const void *b) { + return (((countKeys *)b)->count - ((countKeys *)a)->count); +} + +// keys qsort and unique. +countKeys *uniqsort(uint64_t *possibleKeys, uint32_t size) { + unsigned int i, j = 0; + int count = 0; + countKeys *our_counts; + + qsort(possibleKeys, size, sizeof(uint64_t), compar_int); + + our_counts = calloc(size, sizeof(countKeys)); + if (our_counts == NULL) { + printf("Memory allocation error for our_counts"); + exit(EXIT_FAILURE); + } + + for (i = 0; i < size; i++) { + if (possibleKeys[i + 1] == possibleKeys[i]) { + count++; + } else { + our_counts[j].key = possibleKeys[i]; + our_counts[j].count = count; + j++; + count = 0; + } + } + qsort(our_counts, j, sizeof(countKeys), compar_special_int); + return (our_counts); +} + +// nested decrypt +static void nested_revover(RecPar *rp) { + struct Crypto1State *revstate, * revstate_start = NULL; + uint64_t lfsr = 0; + uint32_t i, kcount = 0; + + rp->keyCount = 0; + rp->keys = NULL; + + for (i = rp->startPos; i < rp->endPos; i++) { + uint32_t nt_probe = rp->pNK[i].ntp; + uint32_t ks1 = rp->pNK[i].ks1; + // And finally recover the first 32 bits of the key + revstate = lfsr_recovery32(ks1, nt_probe ^ rp->authuid); + if (revstate_start == NULL) { + revstate_start = revstate; + } + while ((revstate->odd != 0x0) || (revstate->even != 0x0)) { + lfsr_rollback_word(revstate, nt_probe ^ rp->authuid, 0); + crypto1_get_lfsr(revstate, &lfsr); + // Allocate a new space for keys + if (((kcount % MEM_CHUNK) == 0) || (kcount >= rp->keyCount)) { + rp->keyCount += MEM_CHUNK; + // printf("New chunk by %d, sizeof %lu\n", kcount, key_count * sizeof(uint64_t)); + void *tmp = realloc(rp->keys, rp->keyCount * sizeof(uint64_t)); + if (tmp == NULL) { + printf("Memory allocation error for pk->possibleKeys"); + // exit(EXIT_FAILURE); + rp->keyCount = 0; + return; + } + rp->keys = (uint64_t *)tmp; + } + rp->keys[kcount] = lfsr; + kcount++; + revstate++; + } + free(revstate_start); + revstate_start = NULL; + } + // Truncate + if (kcount != 0) { + rp->keyCount = --kcount; + void *tmp = (uint64_t *)realloc(rp->keys, rp->keyCount * sizeof(uint64_t)); + if (tmp == NULL) { + printf("Memory allocation error for pk->possibleKeys"); + // exit(EXIT_FAILURE); + rp->keyCount = 0; + return; + } + rp->keys = tmp; + return; + } + rp->keyCount = 0; + return; +} + +uint64_t *nested(NtpKs1 *pNK, uint32_t sizePNK, uint32_t authuid, uint32_t *keyCount) { + *keyCount = 0; + uint32_t i; + + RecPar *pRPs = malloc(sizeof(RecPar)); + if (pRPs == NULL) { + return NULL; + } + + pRPs->pNK = pNK; + pRPs->authuid = authuid; + pRPs->startPos = 0; + pRPs->endPos = sizePNK; + + // start recover + nested_revover(pRPs); + *keyCount = pRPs->keyCount; + + uint64_t *keys = NULL; + if (*keyCount != 0) { + keys = malloc(*keyCount * sizeof(uint64_t)); + if (keys != NULL) { + memcpy(keys, pRPs->keys, pRPs->keyCount * sizeof(uint64_t)); + free(pRPs->keys); + } + } + free(pRPs); + + countKeys *ck = uniqsort(keys, *keyCount); + free(keys); + keys = (uint64_t *)NULL; + *keyCount = 0; + + if (ck != NULL) { + for (i = 0; i < TRY_KEYS; i++) { + // We don't known this key, try to break it + // This key can be found here two or more times + if (ck[i].count > 0) { + *keyCount += 1; + void *tmp = realloc(keys, sizeof(uint64_t) * (*keyCount)); + if (tmp != NULL) { + keys = tmp; + keys[*keyCount - 1] = ck[i].key; + } else { + printf("Cannot allocate memory for keys on merge."); + free(keys); + break; + } + } + } + } else { + printf("Cannot allocate memory for ck on uniqsort."); + } + return keys; +} + +// Return 1 if the nonce is invalid else return 0 +uint8_t valid_nonce(uint32_t Nt, uint32_t NtEnc, uint32_t Ks1, uint8_t *parity) { + return ( + (oddparity8((Nt >> 24) & 0xFF) == ((parity[0]) ^ oddparity8((NtEnc >> 24) & 0xFF) ^ BIT(Ks1, 16))) && \ + (oddparity8((Nt >> 16) & 0xFF) == ((parity[1]) ^ oddparity8((NtEnc >> 16) & 0xFF) ^ BIT(Ks1, 8))) && \ + (oddparity8((Nt >> 8) & 0xFF) == ((parity[2]) ^ oddparity8((NtEnc >> 8) & 0xFF) ^ BIT(Ks1, 0))) + ) ? 1 : 0; +} diff --git a/software/src/nested_util.h b/software/src/nested_util.h new file mode 100644 index 00000000..2d569ec7 --- /dev/null +++ b/software/src/nested_util.h @@ -0,0 +1,14 @@ +#ifndef NESTED_H__ +#define NESTED_H__ + +#include "crapto1.h" + +typedef struct { + uint32_t ntp; + uint32_t ks1; +} NtpKs1; + +uint8_t valid_nonce(uint32_t Nt, uint32_t NtEnc, uint32_t Ks1, uint8_t *parity); +uint64_t *nested(NtpKs1 *pNK, uint32_t sizePNK, uint32_t authuid, uint32_t *keyCount); + +#endif diff --git a/software/src/staticnested.c b/software/src/staticnested.c new file mode 100644 index 00000000..f857bfd2 --- /dev/null +++ b/software/src/staticnested.c @@ -0,0 +1,74 @@ +#include +#include +#include +#include +#include "common.h" +#include "nested_util.h" + +int main(int argc, char *const argv[]) { + NtpKs1 *pNK = NULL; + uint32_t i, j, m; + uint32_t nt1, nt2, nttest, ks1, dist; + + uint32_t authuid = atoui(argv[1]); // uid + uint8_t type = (uint8_t)atoui(argv[2]); // target key type + + // process all args. + bool check_st_level_at_first_run = false; + for (i = 3, j = 0; i < argc; i += 2) { + // nt + par + nt1 = atoui(argv[i]); + nt2 = atoui(argv[i + 1]); + + // Which generation of static tag is detected. + if (!check_st_level_at_first_run) { + if (nt1 == 0x01200145) { + // There is no loophole in this generation. + // This tag can be decrypted with the default parameter value 160! + dist = 160; // st gen1 + } else if (nt1 == 0x009080A2) { // st gen2 + // We found that the gen2 tag is vulnerable too but parameter must be adapted depending on the attacked key + if (type == 0x61) { + dist = 161; + } else if (type == 0x60) { + dist = 160; + } else { + // can't be here!!! + goto error; + } + } else { + // can't be here!!! + goto error; + } + check_st_level_at_first_run = true; + } + + nttest = prng_successor(nt1, dist); + ks1 = nt2 ^ nttest; + ++j; + dist += 160; + + void *tmp = realloc(pNK, sizeof(NtpKs1) * j); + if (tmp == NULL) { + goto error; + } + + pNK = tmp; + pNK[j - 1].ntp = nttest; + pNK[j - 1].ks1 = ks1; + } + uint32_t keyCount = 0; + uint64_t *keys = nested(pNK, j, authuid, &keyCount); + + if (keyCount > 0) { + for (i = 0; i < keyCount; i++) { + printf("Key %d... %" PRIx64 " \r\n", i + 1, keys[i]); + fflush(stdout); + } + } + fflush(stdout); + free(keys); + exit(EXIT_SUCCESS); +error: + exit(EXIT_FAILURE); +}