Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .github/workflows/reusable-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,17 @@ jobs:

Test:
runs-on: ubuntu-latest
strategy:
matrix:
# TRS is off by default; run the suite both ways so the transparent
# reader sources and their tests are built and exercised too.
trs: [ OFF, ON ]
steps:
- uses: actions/checkout@v7
with:
submodules: recursive
- name: Configure
run: cmake -DCMAKE_BUILD_TYPE=Debug .
run: cmake -DCMAKE_BUILD_TYPE=Debug -DOPT_BUILD_OSDP_TRS=${{ matrix.trs }} .
- name: Run unit-tests
run: cmake --build . --parallel 7 --target check

Expand Down
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ option(OPT_OSDP_STATIC "Build without dynamic memory allocation" OFF)
option(OPT_OSDP_LIB_ONLY "Only build the library" OFF)
option(OPT_BUILD_BARE_METAL "Build library for bare metal targets" OFF)
option(OPT_USE_32BIT_TICK_T "Use uint32_t tick_t on bare-metal targets" OFF)
option(OPT_BUILD_OSDP_TRS "Enable Transparent Reader Support" OFF)
set(OPT_OSDP_CRYPTO_BACKEND "auto" CACHE STRING
"Crypto backend selection: auto, openssl, mbedtls, or tinyaes")
set_property(CACHE OPT_OSDP_CRYPTO_BACKEND PROPERTY STRINGS
Expand Down
11 changes: 11 additions & 0 deletions configure.sh
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ usage() {
--lib-only Only build the library
--bare-metal Enable bare-metal build paths
--use-32bit-tick-t Use uint32_t tick_t (requires --bare-metal)
--enable-trs Enable Transparent Reader Support (TRS)
--cross-compile PREFIX Use to pass a compiler prefix
--prefix PATH Install path prefix (default: /usr)
--build-dir Build output directory (default: ./build)
Expand Down Expand Up @@ -57,6 +58,7 @@ while [ $# -gt 0 ]; do
--lib-only) LIB_ONLY=1;;
--bare-metal) BARE_METAL=1;;
--use-32bit-tick-t) USE_32BIT_TICK_T=1;;
--enable-trs) ENABLE_TRS=1;;
--build-dir) BUILD_DIR=$2; shift;;
-d|--debug) DEBUG=1;;
-f|--force) FORCE=1;;
Expand Down Expand Up @@ -133,6 +135,10 @@ if [[ ! -z "${USE_32BIT_TICK_T}" ]]; then
CCFLAGS+=" -DUSE_32BIT_TICK_T"
fi

if [[ ! -z "${ENABLE_TRS}" ]]; then
CCFLAGS+=" -DOPT_BUILD_OSDP_TRS"
fi

## Repo meta data
echo "Extracting source code meta information"
PROJECT_VERSION=$(perl -ne 'print if s/^project\(libosdp VERSION ([0-9.]+)\)$/\1/' CMakeLists.txt)
Expand Down Expand Up @@ -214,6 +220,10 @@ fi

TARGETS="cp_app pd_app"

if [[ ! -z "${ENABLE_TRS}" ]]; then
LIBOSDP_SOURCES+=" src/osdp_trs.c"
fi

TEST_SOURCES="tests/unit-tests/test.c"
TEST_SOURCES+=" tests/unit-tests/test-cp-phy.c"
TEST_SOURCES+=" tests/unit-tests/test-pd-phy.c"
Expand All @@ -228,6 +238,7 @@ TEST_SOURCES+=" tests/unit-tests/test-sc-sia-vectors.c"
TEST_SOURCES+=" tests/unit-tests/test-notifications.c"
TEST_SOURCES+=" tests/unit-tests/test-codec-fuzz.c"
TEST_SOURCES+=" tests/unit-tests/test-bio.c"
TEST_SOURCES+=" tests/unit-tests/test-trs.c"
TEST_SOURCES+=" ${LIBOSDP_SOURCES} ${UTILS_SOURCES}"

if [[ ! -z "${LIB_ONLY}" ]]; then
Expand Down
219 changes: 219 additions & 0 deletions include/osdp.h
Original file line number Diff line number Diff line change
Expand Up @@ -910,6 +910,16 @@ enum osdp_notification_type {
* local abort, or a negative status reported by the PD).
*/
OSDP_NOTIFICATION_FILE_TX_DONE,
/**
* TRS card session state change (CP only).
*
* arg0: status -- see enum osdp_trs_session_status_e
*
* Fires when the reader enters transparent mode (OPENED), and once more
* when the session ends, either because a STOP was honoured (CLOSED) or
* because it could not be established or was cut short (FAILED).
*/
OSDP_NOTIFICATION_TRS_STATUS,
};

/**
Expand Down Expand Up @@ -954,9 +964,215 @@ enum osdp_cmd_e {
OSDP_CMD_NOTIFICATION,/**< LibOSDP notification (PD mode, synthesized) */
OSDP_CMD_BIOREAD, /**< Scan and send biometric data command */
OSDP_CMD_BIOMATCH, /**< Scan and match biometric template command */
OSDP_CMD_XWR, /**< Transparent mode command */
OSDP_CMD_SENTINEL /**< Max command value */
};

/**
* @brief Transparent Reader Support (TRS) commands a CP application can issue to
* a smart card in the reader. Set in @c struct osdp_trs_cmd::command and
* submitted as an @c OSDP_CMD_XWR command.
*
* A card session is the band of commands between an @c OSDP_TRS_CMD_START and
* the matching @c OSDP_TRS_CMD_STOP, in submission order:
*
* START, SEND_APDU, SEND_APDU, ..., STOP
*
* The library owns the wire handshakes the band implies (it negotiates
* transparent mode on START, and disconnects the card and restores the reader
* on STOP) and reports the session's progress as @c OSDP_NOTIFICATION_TRS_STATUS
* events. The band is enforced when a command is submitted, so misuse is an
* error from osdp_cp_submit_command() and never a command that runs and then
* has to be taken back: inside a band only TRS commands are accepted (anything
* else would interrupt the card transaction), and a card command is refused
* outside one (it would reach a reader that is not in transparent mode).
*
* To abandon a session and the APDUs still queued for it, flush the command
* queue (osdp_cp_flush_commands()) and then submit @c OSDP_TRS_CMD_STOP; the
* flushed APDUs are reported to the app individually. If the session ends on its
* own before its STOP is reached -- the reader rejects transparent mode, say --
* the APDUs left in the band are completed @c OSDP_COMPLETION_FLUSHED and an
* @c OSDP_TRS_SESSION_FAILED notification says why.
*/
enum osdp_trs_cmd_e {
OSDP_TRS_CMD_START = 1, /**< Open a card session */
OSDP_TRS_CMD_STOP, /**< Close the card session opened by START */
OSDP_TRS_CMD_SEND_APDU, /**< Send a C-APDU to the card */
OSDP_TRS_CMD_ENTER_PIN, /**< EMV PIN entry */
OSDP_TRS_CMD_CARD_SCAN, /**< Scan for a card in the field */
};

/**
* @brief Life-cycle of a TRS card session, reported as @a arg0 of an
* @c OSDP_NOTIFICATION_TRS_STATUS notification.
*/
enum osdp_trs_session_status_e {
OSDP_TRS_SESSION_OPENED = 0, /**< Reader is in transparent mode; APDUs flow */
OSDP_TRS_SESSION_CLOSED, /**< STOP honoured; reader restored */
OSDP_TRS_SESSION_FAILED, /**< PD refused transparent mode, or the
* session was aborted by a link error */
};

/** @brief Max APDU length carried in a TRS command or reply */
#define OSDP_TRS_APDU_MAX_LEN 64
/** @brief Max CSN length carried in a TRS card-info reply */
#define OSDP_TRS_CSN_MAX_LEN 32
/** @brief Max protocol-data length carried in a TRS card-info reply */
#define OSDP_TRS_PROTOCOL_DATA_MAX_LEN 64

struct osdp_trs_apdu {
uint16_t length; /**< APDU length in bytes */
uint8_t data[OSDP_TRS_APDU_MAX_LEN]; /**< APDU bytes */
};

/** @brief Encoding of the PIN digits the reader inserts into the C-APDU */
enum osdp_trs_pin_format_e {
OSDP_TRS_PIN_FORMAT_BINARY = 1, /**< PIN digits as binary values */
OSDP_TRS_PIN_FORMAT_BCD, /**< PIN digits as packed BCD */
OSDP_TRS_PIN_FORMAT_ASCII, /**< PIN digits as ASCII characters */
};

/**
* @brief Conditions that end PIN entry; OR them into
* @c osdp_trs_pin_entry::complete_on.
*/
enum osdp_trs_pin_complete_e {
OSDP_TRS_PIN_COMPLETE_ON_MAX_DIGITS = 1 << 0, /**< max_digits entered */
OSDP_TRS_PIN_COMPLETE_ON_KEY = 1 << 1, /**< Validation (enter) key pressed */
OSDP_TRS_PIN_COMPLETE_ON_TIMEOUT = 1 << 2, /**< Entry timed out */
};

/**
* @brief TRS secure PIN entry request: the reader prompts the user for their
* PIN, inserts it into @a apdu as described by the layout fields below, and
* sends the result to the card.
*
* APDU positions are expressed in bits from the start of the APDU payload.
* Not every position is expressible on the wire: it must be byte-aligned (up
* to 120 bits) or fall within the first 15 bits; anything else fails the
* command submission.
*/
struct osdp_trs_pin_entry {
uint8_t timeout_initial; /**< First-digit timeout in seconds (0 = reader default) */
uint8_t timeout_digit; /**< Per-digit timeout in seconds after the first key */

/**
* The PIN block: the region of the C-APDU where the reader formats
* and inserts the entered PIN.
*/
struct {
enum osdp_trs_pin_format_e format; /**< PIN digit encoding */
bool right_justify; /**< Right-justify the PIN within the block
* (default: left-justified) */
uint16_t offset_bits; /**< Block position in the APDU payload, in bits */
uint8_t size_bytes; /**< Block size in bytes, after justification
* and formatting, as defined by the card
* scheme (8 for EMV/ISO 9564 PIN blocks) */
} pin_block;

/*
* Optional slot in the C-APDU where the reader records how many PIN
* digits the user entered; the app cannot pre-fill it because only
* the reader knows the entered length.
*/
struct {
uint8_t size_bits; /**< Slot size in bits (0 = APDU has no such slot) */
uint16_t offset_bits; /**< Slot position in the APDU payload, in bits */
} pin_length_field;

uint8_t min_digits; /**< Minimum PIN length, in digits */
uint8_t max_digits; /**< Maximum PIN length, in digits */
uint32_t complete_on; /**< When PIN entry ends: OR of
* enum osdp_trs_pin_complete_e conditions */

uint8_t num_messages; /**< Number of display messages */
uint16_t language_id; /**< Display language identifier */
uint8_t msg_index; /**< Index of the message to display */
uint8_t teo_prologue[3]; /**< T=1 protocol prologue */
struct osdp_trs_apdu apdu; /**< C-APDU to send after PIN entry */
};

struct osdp_trs_cmd {
enum osdp_trs_cmd_e command; /**< Which TRS command; selects the union */
union {
struct osdp_trs_apdu apdu; /**< For OSDP_TRS_CMD_SEND_APDU */
struct osdp_trs_pin_entry pin_entry;
};
};

/** @brief Smart-card communication protocol reported in a TRS card-info reply */
enum osdp_trs_card_protocol_e {
OSDP_TRS_CARD_PROTOCOL_CONTACT = 1, /**< ISO 7816 contact (T=0/T=1) */
OSDP_TRS_CARD_PROTOCOL_CONTACTLESS, /**< ISO 14443 A/B contactless */
};

struct osdp_trs_card_info {
uint8_t reader; /**< Reader number (0 = first, 1 = second) */
enum osdp_trs_card_protocol_e protocol; /**< Card communication protocol */
uint8_t csn_len; /**< Length of @a csn in bytes */
uint8_t csn[OSDP_TRS_CSN_MAX_LEN]; /**< Card serial number */
uint8_t protocol_data_len; /**< Length of @a protocol_data in bytes */
/** ATR (contact) or ATS/ATQB (contactless) */
uint8_t protocol_data[OSDP_TRS_PROTOCOL_DATA_MAX_LEN];
};

/** @brief Smart-card presence (and interface) reported in a TRS card-present reply */
enum osdp_trs_card_status_e {
OSDP_TRS_CARD_NOT_PRESENT = 1, /**< No card detected */
OSDP_TRS_CARD_PRESENT, /**< Card present; interface not specified */
OSDP_TRS_CARD_PRESENT_CONTACTLESS, /**< Card present on the contactless (ISO 14443) interface */
OSDP_TRS_CARD_PRESENT_CONTACT, /**< Card present on the contact (ISO 7816) interface */
};

struct osdp_trs_card_present {
uint8_t reader; /**< Reader number (0 = first, 1 = second) */
enum osdp_trs_card_status_e status; /**< Smart-card presence status */
};

struct osdp_trs_card_data {
uint8_t reader; /**< Reader number (0 = first, 1 = second) */
uint8_t status; /**< Result of the APDU exchange as reported by the reader
* (reader-defined; not standardized by OSDP) */
struct osdp_trs_apdu apdu; /**< R-APDU returned by the card */
};

struct osdp_trs_pin_complete {
uint8_t reader; /**< Reader number (0 = first, 1 = second) */
uint8_t status; /**< Result of the secure PIN entry sequence as reported by the
* reader (reader-defined; not standardized by OSDP) */
uint8_t tries; /**< Number of PIN-entry attempts */
};

struct osdp_trs_error {
uint8_t code; /**< Error/NAK condition from the reader or card
* (reader-defined; not standardized by OSDP) */
};

/**
* @brief Transparent Reader Support (TRS) replies delivered to a CP application
* as an @c OSDP_EVENT_TRS event, or submitted by a PD application (via
* @c osdp_pd_submit_event) in answer to a TRS command. The @a reply field
* selects the active union member.
*/
enum osdp_trs_reply_e {
OSDP_TRS_REPLY_CARD_INFO = 1, /**< A card entered the field (CSN, protocol) */
OSDP_TRS_REPLY_CARD_PRESENT, /**< Card-present status for a reader */
OSDP_TRS_REPLY_CARD_DATA, /**< R-APDU returned by the card */
OSDP_TRS_REPLY_PIN_COMPLETE, /**< PIN entry completed */
OSDP_TRS_REPLY_ERROR, /**< Transparent-mode error / NAK from reader */
};

struct osdp_trs_reply {
enum osdp_trs_reply_e reply; /**< Which TRS reply; selects the union */
union {
struct osdp_trs_card_info card_info;
struct osdp_trs_card_present card_present;
struct osdp_trs_card_data card_data;
struct osdp_trs_pin_complete pin_complete;
struct osdp_trs_error error;
};
};

/**
* @brief When set (`struct osdp_cmd::flags`), the command is sent out with the
* OSDP packet broadcast flag to the PD.
Expand Down Expand Up @@ -1000,6 +1216,7 @@ struct osdp_cmd {
struct osdp_notification notif; /**< LibOSDP notification (PD mode) */
struct osdp_cmd_bioread bioread; /**< Biometric read command structure */
struct osdp_cmd_biomatch biomatch; /**< Biometric match command structure */
struct osdp_trs_cmd trs; /**< Transparent mode command structure */
};
};

Expand Down Expand Up @@ -1194,6 +1411,7 @@ enum osdp_event_type {
OSDP_EVENT_MFGERRR, /**< Manufacturer specific error reply event */
OSDP_EVENT_BIOREADR, /**< Scan and send biometric data event */
OSDP_EVENT_BIOMATCHR, /**< Scan and match biometric template event */
OSDP_EVENT_TRS, /**< Transparent mode response event */
OSDP_EVENT_SENTINEL /**< Max event value */
};

Expand All @@ -1215,6 +1433,7 @@ struct osdp_event {
struct osdp_event_biomatchr biomatchr; /**< Biometric match reply event structure */
struct osdp_status_report status; /**< Status report event structure */
struct osdp_notification notif; /**< LibOSDP notification (CP mode) */
struct osdp_trs_reply trs; /**< Transparent mode reply event structure */
};
};

Expand Down
1 change: 1 addition & 0 deletions library.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"srcFilter": [
"+<**/*.c>",
"-<osdp_diag.c>",
"-<osdp_trs.c>",
"-<crypto/mbedtls.c>",
"-<crypto/openssl.c>",
"+<../utils/src/disjoint_set.c>",
Expand Down
11 changes: 11 additions & 0 deletions python/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ def try_vendor_sources(src_dir, src_files, vendor_dir):
"src/osdp_common.h",
"src/osdp_file.h",
"src/osdp_metrics.h",
"src/osdp_trs.h",
"src/crypto/tinyaes_src.h",
]

Expand All @@ -168,6 +169,9 @@ def try_vendor_sources(src_dir, src_files, vendor_dir):
"src/osdp_diag.h",
"utils/include/utils/pcap_gen.h",
"utils/src/pcap_gen.c",

# Optional when TRS is enabled
"src/osdp_trs.c",
]

# LICENSE lives at the repo root; vendor a copy so wheel/sdist builds
Expand All @@ -176,6 +180,8 @@ def try_vendor_sources(src_dir, src_files, vendor_dir):

definitions = [
"OPT_OSDP_PACKET_TRACE",
# osdp_sys exposes no TRS bindings yet; keep TRS out of the extension.
# "OPT_BUILD_OSDP_TRS",
# "OPT_OSDP_DATA_TRACE",
# "OPT_OSDP_SKIP_MARK_BYTE",
]
Expand All @@ -195,6 +201,11 @@ def try_vendor_sources(src_dir, src_files, vendor_dir):
"utils/src/pcap_gen.c",
]

if "OPT_BUILD_OSDP_TRS" in definitions:
source_files += [
"src/osdp_trs.c",
]

source_files = add_prefix_to_path(source_files, "vendor")

include_dirs = [
Expand Down
Loading