Rust Ledger APDU通信库的使用,实现安全硬件钱包APDU协议交互

Rust Ledger APDU通信库的使用,实现安全硬件钱包APDU协议交互

概述

ledger-apdu 是一个用于与 Ledger Nano S/X 设备通信的 Rust 实用库。它提供了与硬件钱包进行 APDU (Application Protocol Data Unit) 协议交互的功能。

安装

在您的项目目录中运行以下 Cargo 命令:

cargo add ledger-apdu

或者在 Cargo.toml 中添加以下行:

ledger-apdu = "0.11.0"

示例代码

以下是使用 ledger-apdu 库与 Ledger 设备进行 APDU 通信的基本示例:

use ledger_apdu::{APDUCommand, APDUError};
use std::io::{Read, Write};

// 假设我们有一个实现了 Read 和 Write trait 的 Ledger 设备连接
fn send_apdu_command<T: Read + Write>(device: &mut T, command: APDUCommand) -> Result<Vec<u8>, APDUError> {
    // 将 APDU 命令转换为字节数组
    let command_bytes = command.serialize();
    
    // 发送命令到设备
    device.write_all(&command_bytes)?;
    
    // 读取响应
    let mut response = [0u8; 260]; // APDU 响应最大长度
    let len = device.read(&mut response)?;
    
    // 检查状态码
    if len < 2 {
        return Err(APDUError::InvalidResponse);
    }
    
    let status_code = u16::from_be_bytes([response[len-2], response[len-1]]);
    if status_code != 0x9000 {
        return Err(APDUError::StatusCode(status_code));
    }
    
    // 返回响应数据(不包括状态码)
    Ok(response[..len-2].to_vec())
}

fn main() {
    // 示例:获取设备版本信息
    let get_version_command = APDUCommand {
        cla: 0xE0,  // 指令类
        ins: 0x01,  // 指令码
        p1: 0x00,   // 参数1
        p2: 0x00,   // 参数2
        data: vec![], // 数据字段
        le: Some(0x00), // 期望的响应长度
    };
    
    // 在实际应用中,您需要先建立与 Ledger 设备的连接
    // let mut device = connect_to_ledger();
    
    // match send_apdu_command(&mut device, get_version_command) {
    //     Ok(response) => println!("Version: {:?}", response),
    //     Err(e) => eprintln!("Error: {:?}", e),
    // }
}

完整示例

以下是一个更完整的示例,展示了如何与 Ledger 设备进行交互:

use ledger_apdu::{APDUCommand, APDUError};
use std::time::Duration;
use serialport::{self, SerialPort};

// 连接到 Ledger 设备
fn connect_to_ledger() -> Result<Box<dyn SerialPort>, APDUError> {
    // 在实际应用中,您需要确定正确的端口名称
    let ports = serialport::available_ports().map_err(|_| APDUError::DeviceNotFound)?;
    
    for port in ports {
        if let Ok(mut port) = serialport::new(&port.port_name, 115_200)
            .timeout(Duration::from_millis(1000))
            .open()
        {
            // 简单的设备检测 - 发送测试命令
            let test_command = APDUCommand {
                cla: 0xE0,
                ins: 0x01,
                p1: 0x00,
                p2: 0x00,
                data: vec![],
                le: Some(0x00),
            };
            
            if send_apdu_command(&mut port, test_command).is_ok() {
                return Ok(port);
            }
        }
    }
    
    Err(APDUError::DeviceNotFound)
}

// 获取设备信息
fn get_device_info(device: &mut dyn SerialPort) -> Result<(), APDUError> {
    // 获取设备版本
    let version_command = APDUCommand {
        cla: 0xE0,
        ins: 0x01,
        p1: 0x00,
        p2: 0x00,
        data: vec![],
        le: Some(0x00),
    };
    
    let version = send_apdu_command(device, version_command)?;
    println!("Device version: {:?}", version);
    
    // 获取设备ID
    let device_id_command = APDUCommand {
        cla: 0xE0,
        ins: 0x02,
        p1: 0x00,
        p2: 0x00,
        data: vec![],
        le: Some(0x00),
    };
    
    let device_id = send_apdu_command(device, device_id_command)?;
    println!("Device ID: {:?}", device_id);
    
    Ok(())
}

fn main() {
    // 连接到设备
    match connect_to_ledger() {
        Ok(mut device) => {
            println!("Connected to Ledger device");
            
            // 获取设备信息
            if let Err(e) = get_device_info(&mut device) {
                eprintln!("Error getting device info: {:?}", e);
            }
        }
        Err(e) => eprintln!("Failed to connect to Ledger device: {:?}", e),
    }
}

注意事项

  1. 在实际使用中,您需要根据具体的 Ledger 应用程序实现正确的 APDU 命令
  2. 不同的加密货币应用可能有不同的指令集
  3. 确保正确处理所有可能的错误情况
  4. 考虑实现重试机制以提高通信可靠性

1 回复

Rust Ledger APDU通信库使用指南

概述

ledger-apdu是一个用于与Ledger硬件钱包进行APDU协议通信的Rust库。APDU(Application Protocol Data Unit)是智能卡和硬件钱包常用的通信协议格式。

安装

Cargo.toml中添加依赖:

[dependencies]
ledger-apdu = "0.1"

基本使用方法

1. 创建APDU命令

use ledger_apdu::{APDUCommand, APDUError};

fn create_apdu_command() -> Result<APDUCommand, APDUError> {
    let cla = 0xE0; // 指令类别
    let ins = 0x02; // 指令代码
    let p1 = 0x00;  // 参数1
    let p2 = 0x00;  // 参数2
    let data = vec![0x01, 0x02, 0x03]; // 数据字段
    let le = 0x00;  // 期望的响应长度
    
    APDUCommand::new(cla, ins, p1, p2, data, le)
}

2. 解析APDU响应

use ledger_apdu::{APDUAnswer, APDUError};

fn parse_apdu_response(response: Vec<u8>) -> Result<APDUAnswer, APDUError> {
    APDUAnswer::from_answer(response)
}

完整示例:查询硬件钱包版本

use ledger_apdu::{APDUCommand, APDUAnswer};
use std::error::Error;

fn get_ledger_version() -> Result<String, Box<dyn Error>> {
    // 创建获取版本的APDU命令
    let command = APDUCommand {
        cla: 0xE0,
        ins: 0x01, // GET_VERSION 指令
        p1: 0x00,
        p2: 0x00,
        data: Vec::new(),
        le: 0x00,
    };
    
    // 这里需要实际的传输层实现,例如通过HID或USB
    // let response = transport.exchange(&command)?;
    
    // 模拟响应
    let mock_response = vec![
        0x31, 0x2e, 0x36, 0x2e, 0x30, // 版本号 "1.6.0"
        0x90, 0x00 // SW_OK
    ];
    
    let answer = APDUAnswer::from_answer(mock_response)?;
    
    if answer.retcode() != 0x9000 {
        return Err("Command failed".into());
    }
    
    // 将响应数据转换为字符串
    let version = String::from_utf8(answer.data().to_vec())?;
    
    Ok(version)
}

fn main() {
    match get_ledger_version() {
        Ok(version) => println!("Ledger version: {}", version),
        Err(e) => eprintln!("Error: {}", e),
    }
}

高级功能

扩展APDU命令

use ledger_apdu::APDUCommand;

fn create_extended_apdu() -> APDUCommand {
    // 大数据量的APDU命令
    let large_data = vec![0u8; 1024]; // 1KB数据
    
    APDUCommand {
        cla: 0xE0,
        ins: 0x08, // 大数据传输指令
        p1: 0x00,
        p2: 0x00,
        data: large_data,
        le: 0x00,
    }
}

处理分块响应

use ledger_apdu::{APDUCommand, APDUAnswer};

fn handle_chunked_response(transport: &impl Transport) -> Result<Vec<u8>, Box<dyn Error>> {
    let mut full_response = Vec::new();
    let mut offset = 0;
    
    loop {
        let command = APDUCommand {
            cla: 0xE0,
            ins: 0x10, // 读取分块指令
            p1: (offset >> 8) as u8,
            p2: offset as u8,
            data: Vec::new(),
            le: 0x00,
        };
        
        let response = transport.exchange(&command)?;
        let answer = APDUAnswer::from_answer(response)?;
        
        if answer.retcode() == 0x6C00 {
            // 需要指定长度重试
            continue;
        }
        
        full_response.extend_from_slice(answer.data());
        
        if answer.retcode() != 0x9000 && answer.retcode() != 0x6100 {
            break;
        }
        
        offset += answer.data().len() as u16;
    }
    
    Ok(full_response)
}

错误处理

use ledger_apdu::APDUError;

fn handle_apdu_error(error: APDUError) {
    match error {
        APDUError::InvalidLength => eprintln!("Invalid APDU length"),
        APDUError::InvalidResponse => eprintln!("Invalid response format"),
        APDUError::IoError(e) => eprintln!("IO error: {}", e),
        _ => eprintln!("Unknown APDU error"),
    }
}

注意事项

  1. 实际使用时需要实现特定的传输层(USB/HID/BLE等)
  2. 不同Ledger应用(比特币/以太坊等)有不同的APDU指令集
  3. 生产环境需要正确处理所有可能的错误码
  4. 大数据传输需要考虑分块处理

这个库提供了基础的APDU协议处理能力,实际与Ledger设备通信还需要结合具体的传输层实现。

完整示例Demo

下面是一个完整的与Ledger设备交互的示例,包含传输层模拟实现:

use ledger_apdu::{APDUCommand, APDUAnswer};
use std::error::Error;

// 模拟传输层
struct MockTransport;

impl MockTransport {
    fn exchange(&self, command: &APDUCommand) -> Result<Vec<u8>, Box<dyn Error>> {
        // 模拟不同命令的响应
        match command.ins {
            0x01 => Ok(vec![0x31, 0x2e, 0x36, 0x2e, 0x30, 0x90, 0x00]), // 版本响应
            0x02 => Ok(vec![0x01, 0x02, 0x03, 0x90, 0x00]), // 通用响应
            _ => Ok(vec![0x6F, 0x00]), // 未知指令错误
        }
    }
}

fn main() -> Result<(), Box<dyn Error>> {
    let transport = MockTransport;
    
    // 示例1: 获取版本
    let version_cmd = APDUCommand {
        cla: 0xE0,
        ins: 0x01,
        p1: 0x00,
        p2: 0x00,
        data: Vec::new(),
        le: 0x00,
    };
    
    let response = transport.exchange(&version_cmd)?;
    let answer = APDUAnswer::from_answer(response)?;
    let version = String::from_utf8(answer.data().to_vec())?;
    println!("Ledger version: {}", version);
    
    // 示例2: 发送数据
    let data_cmd = APDUCommand {
        cla: 0xE0,
        ins: 0x02,
        p1: 0x00,
        p2: 0x00,
        data: vec![0xAA, 0xBB, 0xCC],
        le: 0x00,
    };
    
    let response = transport.exchange(&data_cmd)?;
    let answer = APDUAnswer::from_answer(response)?;
    println!("Received data: {:?}", answer.data());
    
    Ok(())
}

这个完整示例展示了:

  1. 模拟传输层实现
  2. 创建不同类型的APDU命令
  3. 处理设备响应
  4. 错误处理机制

实际使用时,您需要根据具体的硬件连接方式实现真实的传输层。

回到顶部