pcsc-rust: Rust Bindings for PC/SC API

repository·master·Indexed 40 days ago

https://github.com/bluetech/pcsc-rust

Safe Rust bindings to the PC/SC (Personal Computer/Smart Card) API for smart card communication across Linux, Windows, and macOS. Version 2.9.0 provides high-level wrappers in the `pcsc` crate and low-level FFI bindings in `pcsc-sys` to establish contexts, list readers, connect to cards, and transmit APDU commands.

Tokens
5.4K
Snippets
20
Records
24
Agent score
100%

What's inside pcsc-rust

  1. Understand the pcsc-rust crate structure

    master

    The project is split into two main crates:

    • pcsc-sys: Contains direct, low-level FFI bindings to the C PC/SC API.
    • pcsc: Contains high-level, safe Rust wrappers around the low-level bindings. Most users should use the pcsc crate.
  2. How thread safety and blocking operations work in pcsc-rust

    master

    A library Context can be safely moved to another thread or cloned and used from multiple threads. However, operations on a single context are not performed concurrently; if one thread performs a blocking operation (like get_status_change()), other operations on that same context will block until the first one finishes.

    To achieve true concurrency (e.g., monitoring reader changes in one thread while sending commands in another), you should create a separate Context for each thread.

    Important Note on Card Access: Even if you use different contexts, if one context holds an exclusive transaction with a card, any operation on that same underlying card from a different context will block until the transaction is complete.

  3. Connect to a smart card and transmit an APDU

    master

    This example demonstrates the standard workflow for smart card interaction using pcsc-rust:

    1. Establish a Context using Scope::User.
    2. List available readers using ctx.list_readers().
    3. Connect to a card in a specific reader using ctx.connect() with a ShareMode and Protocols.
    4. Transmit an APDU command using card.transmit() and capture the response in a buffer.
    use pcsc::*;
    
    fn main() {
        // Establish a PC/SC context.
        let ctx = match Context::establish(Scope::User) {
            Ok(ctx) => ctx,
            Err(err) => {
                eprintln!("Failed to establish context: {}", err);
                std::process::exit(1);
            }
        };
    
        // List available readers.
        let mut readers_buf = [0; 2048];
        let mut readers = match ctx.list_readers(&mut readers_buf) {
            Ok(readers) => readers,
            Err(err) => {
                eprintln!("Failed to list readers: {}", err);
                std::process::exit(1);
            }
        };
    
        // Use the first reader.
        let reader = match readers.next() {
            Some(reader) => reader,
            None => {
                println!("No readers are connected.");
                return;
            }
        };
        println!("Using reader: {:?}", reader);
    
        // Connect to the card.
        let card = match ctx.connect(reader, ShareMode::Shared, Protocols::ANY) {
            Ok(card) => card,
            Err(Error::NoSmartcard) => {
                println!("A smartcard is not present in the reader.");
                return;
            }
            Err(err) => {
                eprintln!("Failed to connect to card: {}", err);
                std::process::exit(1);
            }
        };
    
        // Send an APDU command.
        let apdu = b"\x00\xa4\x04\x00\x0A\xA0\x00\x00\x00\x62\x03\x01\x0C\x06\x01";
        println!("Sending APDU: {:?}", apdu);
        let mut rapdu_buf = [0; MAX_BUFFER_SIZE];
        let rapdu = match card.transmit(apdu, &mut rapdu_buf) {
            Ok(rapdu) => rapdu,
            Err(err) => {
                eprintln!("Failed to transmit APDU command to card: {}", err);
                std::process::exit(1);
            }
        };
        println!("APDU response: {:?}", rapdu);
    }
  4. Basic usage pattern for pcsc-rust

    master

    To interact with a smartcard using pcsc-rust, follow this lifecycle:

    1. Establish a Context using Context::establish(Scope).
    2. List available readers using ctx.list_readers(&mut buffer).
    3. Connect to a specific reader using ctx.connect(reader, ShareMode, Protocols).
    4. Transmit APDU commands to the card using card.transmit(apdu, &mut buffer).

    Common error handling includes checking for Error::NoSmartcard when attempting to connect to a reader that has no card inserted.

    use pcsc::*;
    
    fn main() {
        // 1. Establish a PC/SC context.
        let ctx = match Context::establish(Scope::User) {
            Ok(ctx) => ctx,
            Err(err) => {
                eprintln!("Failed to establish context: {}", err);
                std::process::exit(1);
            }
        };
    
        // 2. List available readers.
        let mut readers_buf = [0; 2048];
        let mut readers = match ctx.list_readers(&mut readers_buf) {
            Ok(readers) => readers,
            Err(err) => {
                eprintln!("Failed to list readers: {}", err);
                std::process::exit(1);
            }
        };
    
        // 3. Use the first reader.
        let reader = match readers.next() {
            Some(reader) => reader,
            None => {
                println!("No readers are connected.");
                return;
            }
        };
        println!("Using reader: {:?}", reader);
    
        // 4. Connect to the card.
        let card = match ctx.connect(reader, ShareMode::Shared, Protocols::ANY) {
            Ok(card) => card,
            Err(Error::NoSmartcard) => {
                println!("A smartcard is not present in the reader.");
                return;
            }
            Err(err) => {
                eprintln!("Failed to connect to card: {}", err);
                std::process::exit(1);
            }
        };
    
        // 5. Send an APDU command.
        let apdu = b"\x00\xa4\x04\x00\x0A\xA0\x00\x00\x00\x62\x03\x01\x0C\x06\x01";
        println!("Sending APDU: {:?}", apdu);
        let mut rapdu_buf = [0; MAX_BUFFER_SIZE];
        let rapdu = match card.transmit(apdu, &mut rapdu_buf) {
            Ok(rapdu) => rapdu,
            Err(err) => {
                eprintln!("Failed to transmit APDU command to card: {}", err);
                std::process::exit(1);
            }
        };
        println!("APDU response: {:?}", rapdu);
    }
  5. Connect to a smart card

    master

    Use connect() to establish a connection to a card in a specific reader. You must provide the reader name (as a &CStr), the ShareMode, and the Protocols you prefer. This returns a Card object which provides the interface for communicating with the card.

    let card = context.connect(&reader_name, ShareMode::Exclusive, Protocols::T0 | Protocols::T1)?;
  6. List connected card readers

    master

    To list available card readers, you must first determine the required buffer size using list_readers_len(), then provide a buffer of that size to list_readers(). list_readers() returns a ReaderNames iterator that yields &CStr values pointing directly into the provided buffer.

    let len = context.list_readers_len()?;
    let mut buffer = vec![0u8; len];
    let readers = context.list_readers(&mut buffer)?;
    
    for reader in readers {
        println!("Found reader: {:?}", reader);
    }
  7. Connect to a card with Context::connect

    master

    Use ctx.connect(reader, share_mode, protocols) to establish a connection to a smartcard in a specific reader.

    Parameters:

    • reader: The name of the reader to connect to.
    • share_mode: Defines how the card is shared (e.g., ShareMode::Shared).
    • protocols: The communication protocol to use (e.g., Protocols::ANY).

    Returns a Card object on success. If no card is present, it returns Err(Error::NoSmartcard).

    let card = ctx.connect(reader, ShareMode::Shared, Protocols::ANY).expect("Failed to connect");
  8. Monitor card reader and card state changes

    master

    Use get_status_change() to block the current thread until a state change occurs in the specified readers. This is useful for detecting card insertions or removals.

    To detect any reader insertion or removal (Plug and Play), use the special reader name \?PnP?\Notification, which can be obtained via PNP_NOTIFICATION().

    After the function returns, you must call sync_current_state() on your ReaderState objects to update their internal state to match the newly reported event state.

    let mut states = [ReaderState::new("Reader Name", State::UNAWARE)];
    // Blocks until change
    context.get_status_change(None, &mut states)?;
    
    for state in &mut states {
        println!("New state: {:?}", state.event_state());
        state.sync_current_state();
    }
  9. List available readers with list_readers

    master

    The ctx.list_readers(&mut buffer) method retrieves a list of available smartcard readers. You must provide a mutable byte buffer to hold the reader names.

    Returns an iterator of reader names on success.

    let mut readers_buf = [0; 2048];
    let mut readers = ctx.list_readers(&mut readers_buf).expect("Failed to list readers");
  10. Manage card transactions

    master

    To ensure atomic operations on a card, use transaction() or transaction2(). These methods return a Transaction object that holds an exclusive lock on the card.

    • transaction(): Returns a Transaction. If the transaction fails, the Card object is consumed by the error.
    • transaction2(): Returns Result<Transaction, (&mut Self, Error)>. This allows you to recover the Card reference if the transaction fails, which is useful for retrying after a ResetCard error.

    To end a transaction, call end(disposition). If you don't call end(), the Transaction will automatically end using Disposition::LeaveCard when it is dropped.

    // Using transaction2 to allow recovery on error
    let mut card = context.connect(...)?;
    let tx_result = card.transaction2();
    
    match tx_result {
        Ok(mut tx) => {
            // Perform operations via the transaction (which derefs to Card)
            tx.transmit(&[0x00, 0x00], &mut buf)?;
            tx.end(Disposition::LeaveCard)?;
        }
        Err((card_ref, err)) => {
            // Recover the card and handle error
            println!("Transaction failed: {:?}", err);
        }
    }