emerald-vault-core 0.26.6

Ethereum secure account management core libary
Documentation
/*
Copyright 2019 ETCDEV GmbH

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
//! # Module providing commnication using HID API
//!

use super::apdu::APDU;
use super::error::Error;
use super::to_arr;
use super::HidDevice;
use log;
use std::cmp::min;
use std::mem::size_of_val;
use std::slice;
use hex;
use hidapi::HidDeviceInfo;

///
pub const HID_RPT_SIZE: usize = 64;

///
pub const INIT_HEADER_SIZE: usize = 7;

/// Size of data chunk expected in Init USB HID Packets
const INIT_DATA_SIZE: usize = HID_RPT_SIZE - 12;

/// Size of data chunk expected in Cont USB HID Packets
const CONT_DATA_SIZE: usize = HID_RPT_SIZE - 5;

/// ISO 7816-4 defined response status words
pub const SW_NO_ERROR: [u8; 2] = [0x90, 0x00];
pub const SW_CONDITIONS_NOT_SATISFIED: [u8; 2] = [0x69, 0x85];
pub const SW_WRONG_DATA: [u8; 2] = [0x6A, 0x80];
pub const SW_WRONG_LENGTH: [u8; 2] = [0x67, 0x00];
pub const SW_INCORRECT_PARAMETERS: [u8; 2] = [0x6b, 0x00];
pub const SW_USER_CANCEL: [u8; 2] = [0x6A, 0x85];

/// Packs header with Ledgers magic numbers
/// For more details refer APDU doc:
/// <https://github.com/LedgerHQ/blue-app-eth/blob/master/doc/ethapp.asc#general-purpose-apdus>
///
fn get_hid_header(channel: u16, index: usize) -> [u8; 5] {
    [
        (channel >> 8) as u8, (channel & 0xff) as u8, //channel
        0x05, //tag
        (index >> 8) as u8, (index & 0xff) as u8 //length
    ]
}

///
fn check_recv_frame(frame: &[u8], channel: u16, index: usize) -> Result<(), Error> {
    if size_of_val(frame) < 5
        || frame[0] != (channel >> 8) as u8 || frame[1] != (channel & 0xff) as u8
        || frame[2] != 0x05 {
        return Err(Error::CommError("Invalid frame header size".to_string()));
    }

    let seq = (frame[3] as usize) << 8 | (frame[4] as usize);
    if seq != index {
       return Err(Error::CommError(format!("Invalid sequence. {:?} != {:?}", seq, index)));
    }

    if index == 0 && size_of_val(frame) < 7 {
        return Err(Error::CommError("Invalid frame size".to_string()));
    }

    Ok(())
}

fn get_init_header(apdu: &APDU) -> [u8; INIT_HEADER_SIZE] {
    let mut buf = Vec::with_capacity(INIT_HEADER_SIZE);
    buf.extend_from_slice(&[(apdu.len() >> 8) as u8, (apdu.len() & 0xff) as u8]);
    buf.extend_from_slice(&apdu.raw_header());
    to_arr(&buf)
}

fn set_data(data: &mut [u8], itr: &mut slice::Iter<u8>, max: usize) {
    let available = itr.size_hint().0;

    for i in 0..min(max, available) {
        data[i] = *itr.next().unwrap();
    }
}

/// Check `status word`, if invalid coverts it
/// to the proper error message
fn sw_to_error(sw_h: u8, sw_l: u8) -> Result<(), Error> {
    match [sw_l, sw_h] {
        SW_NO_ERROR => Ok(()),
        SW_WRONG_LENGTH => Err(Error::CommError("Incorrect length".to_string())),
        SW_WRONG_DATA => Err(Error::CommError("Invalid data".to_string())),
        SW_INCORRECT_PARAMETERS => Err(Error::CommError("Incorrect parameters".to_string())),
        SW_USER_CANCEL => Err(Error::CommError("Canceled by user".to_string())),
        SW_CONDITIONS_NOT_SATISFIED => {
            Err(Error::CommError("Conditions not satisfied()".to_string()))
        }
        v => Err(Error::CommError(format!(
            "Internal communication error: {:?}",
            v
        ))),
    }
}

///
pub fn sendrecv(dev: &HidDevice, info: &HidDeviceInfo, apdu: &APDU) -> Result<Vec<u8>, Error> {
    let mut frame_index: usize = 0;
    let mut data_itr = apdu.data.iter();
    let mut init_sent = false;
    let channel = info.usage_page;

    debug!(">> senrecv input: {:?}", &apdu);
    // Write Data.
    while data_itr.size_hint().0 != 0 {
        // Add 1 to HID_RPT_SIZE since we need to prefix this with a record
        // index.
        let mut frame: [u8; (HID_RPT_SIZE + 1) as usize] = [0; (HID_RPT_SIZE + 1) as usize];

        frame[1..6].clone_from_slice(&get_hid_header(channel, frame_index));
        if !init_sent {
            frame[6..13].clone_from_slice(&get_init_header(apdu));
            init_sent = true;
            set_data(&mut frame[13..], &mut data_itr, INIT_DATA_SIZE);
        } else {
            set_data(&mut frame[6..], &mut data_itr, CONT_DATA_SIZE);
        }

        if log_enabled!(log::Level::Trace) {
            let parts: Vec<String> = frame.iter().map(|byte| format!("{:02x}", byte)).collect();
            trace!(">> USB send: {}", parts.join(""));
        }

        if let Err(err) = dev.write(&frame) {
            return Err(err.into());
        };
        frame_index += 1;
    }

    debug!("\t |- read response");
    frame_index = 0;
    let mut data: Vec<u8> = Vec::new();
    let datalen: usize;
    let mut recvlen: usize = 0;
    let mut frame: [u8; HID_RPT_SIZE] = [0u8; HID_RPT_SIZE];
    let frame_size = dev.read(&mut frame)?;

    check_recv_frame(&frame, channel, frame_index)?;
    datalen = (frame[5] as usize) << 8 | (frame[6] as usize);
    data.extend_from_slice(&frame[7..frame_size]);

    recvlen += frame_size;
    frame_index += 1;
    debug!(
        "\t\t|-- init data: {:?}, recvlen: {}, datalen: {}",
        hex::encode(&data), recvlen, datalen
    );

    while recvlen < datalen {
        frame = [0u8; HID_RPT_SIZE];
        let frame_size = dev.read(&mut frame)?;

        check_recv_frame(&frame, channel, frame_index)?;
        data.extend_from_slice(&frame[5..frame_size]);
        recvlen += frame_size;
        frame_index += 1;
        debug!(
            "\t\t|-- cont_{:?} size:{:?}, data: {:?}",
            frame_index,
            data.len(),
            hex::encode(&data)
        );
    }
    data.truncate(datalen);
    match sw_to_error(data.pop().unwrap(), data.pop().unwrap()) {
        Ok(_) => Ok(data),
        Err(e) => Err(e),
    }
}

pub fn ping(dev: &HidDevice, info: &HidDeviceInfo) -> Result<bool, Error> {
    let mut frame: [u8; (HID_RPT_SIZE + 1) as usize] = [0; (HID_RPT_SIZE + 1) as usize];
    let channel = info.usage_page;
    frame[1] = (channel >> 8) as u8;
    frame[2] = (channel & 0xff) as u8;
    frame[3] = 2;
    if log_enabled!(log::Level::Trace) {
        let parts: Vec<String> = frame.iter().map(|byte| format!("{:02x}", byte)).collect();
        trace!(">> PING USB send: {}", parts.join(""));
    }
    if let Err(err) = dev.write(&frame) {
        return Err(err.into());
    };
    let mut frame: [u8; HID_RPT_SIZE] = [0u8; HID_RPT_SIZE];
    let frame_size = dev.read(&mut frame)?;
    let mut data: Vec<u8> = Vec::new();
    data.extend_from_slice(&frame[7..frame_size]);

    debug!(
        "\t\t|-- PING response: {:?}",
        hex::encode(&data)
    );

    Ok(data[0] == 0)
}