Initial commit

This commit is contained in:
Nicolas Stalder
2021-05-14 00:01:42 +02:00
commit b679730eb6
14 changed files with 1712 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
# This is a configuration file for the direnv tool
# More info at <https://direnv.net>
export SOLO2_LOG=info
+1
View File
@@ -0,0 +1 @@
/target
Generated
+1400
View File
File diff suppressed because it is too large Load Diff
+29
View File
@@ -0,0 +1,29 @@
[package]
name = "solo2-cli"
version = "0.0.0-unreleased"
authors = ["SoloKeys Developers"]
edition = "2018"
[lib]
name = "solo2"
path = "src/lib.rs"
[[bin]]
name = "solo2"
required-features = ["cli"]
[dependencies]
log = "0.4.14"
anyhow = "1.0.40"
clap = "2.33.3"
hex = "0.4.3"
hex-literal = "0.3.1"
iso7816 = { git = "https://github.com/ycrypto/iso7816", branch = "main" }
lazy_static = "1.4.0"
# lpc55 = "0.1.0-alpha.1"
lpc55 = { git = "https://github.com/lpc55/lpc55-host", branch = "main" }
pcsc = "2.4.0"
pretty_env_logger = { version = "0.4.0", optional = true }
[features]
cli = ["pretty_env_logger"]
+3
View File
@@ -0,0 +1,3 @@
build:
cargo build --release --features cli --bin solo2
ls -sh target/release/solo2
+4
View File
@@ -0,0 +1,4 @@
# solo2 library and CLI
Example: `solo2 app management` selects the management app.
If `SOLO2_LOG=info` is set in the environment, shows the serial number of the key.
+23
View File
@@ -0,0 +1,23 @@
# This is a configuration file for the bacon tool
# More info at <https://github.com/Canop/bacon>
default_job = "check-bin"
[jobs]
[jobs.check-bin]
command = ["cargo", "check", "--features", "cli", "--bin", "solo2", "--color", "always"]
need_stdout = false
[jobs.check-lib]
command = ["cargo", "check", "--color", "always"]
need_stdout = false
[jobs.clippy]
command = ["cargo", "clippy", "--color", "always"]
need_stdout = false
[jobs.test]
command = ["cargo", "test", "--color", "always"]
need_stdout = true
+59
View File
@@ -0,0 +1,59 @@
use hex_literal::hex;
use crate::{Card, Result};
use pcsc::{Context, Protocols, Scope, ShareMode};
pub mod management;
pub const SOLOKEYS_RID: &'static [u8] = &hex!("A000000847");
pub const MANAGEMENT_PIX: &'static [u8] = &hex!("00000001");
pub const TESTER_PIX: &'static [u8] = &hex!("01000000");
pub const PROVISIONER_PIX: &'static [u8] = &hex!("01000001");
pub trait App: Sized {
const RID: &'static [u8];
const PIX: &'static [u8];
fn aid() -> Vec<u8> {
let mut aid: Vec<u8> = Default::default();
aid.extend_from_slice(Self::RID);
aid.extend_from_slice(Self::PIX);
aid
}
fn select(&mut self) -> Result<Vec<u8>> {
// use iso7816::command::class::Class;
info!("selecting app: {}", hex::encode(Self::aid()).to_uppercase());
self.card().call(
// Class::
0,
iso7816::Instruction::Select.into(),
0x04,
0x00,
Some(&Self::aid()),
)
}
fn card(&mut self) -> &mut Card;
fn connect() -> Result<Card> {
let context = Context::establish(Scope::User)?;
let l = context.list_readers_len()?;
let mut buffer = Vec::with_capacity(l);
buffer.resize(l, 0);
let readers = context.list_readers(&mut buffer)?.collect::<Vec<_>>();
// TODO: select (by UUID)
let reader = readers[0];
info!("connecting with reader: `{}`", &reader.to_string_lossy());
let card = Card::from(context.connect(reader, ShareMode::Shared, Protocols::ANY)?);
info!("...connected");
Ok(card)
}
fn new() -> Result<Self>;
}
+22
View File
@@ -0,0 +1,22 @@
use crate::{Card, Result};
pub struct App {
card: Card,
}
impl super::App for App {
const RID: &'static [u8] = super::SOLOKEYS_RID;
const PIX: &'static [u8] = super::PROVISIONER_PIX;
fn new() -> Result<Self> {
Ok(Self {
card: Self::connect()?,
})
}
fn card(&mut self) -> &mut Card {
&mut self.card
}
}
impl App {}
+64
View File
@@ -0,0 +1,64 @@
use clap::{self, crate_authors, crate_version, App, Arg, SubCommand};
const ABOUT: &str = "
solo2 is the go-to tool to interact with a Solo 2 security key.
Use -h for short descriptions and --help for more details
Project homepage: https://github.com/solokeys/solo2-cli
";
pub fn cli() -> clap::App<'static, 'static> {
lazy_static::lazy_static! {
static ref LONG_VERSION: String = long_version(None);
}
let cli = App::new("solo2")
.author(crate_authors!())
.version(crate_version!())
.long_version(LONG_VERSION.as_str())
.about(ABOUT)
.help_message("Prints help information. Use --help for more details.")
.setting(clap::AppSettings::SubcommandRequiredElseHelp)
// apps
.subcommand(
SubCommand::with_name("app")
.about("app interactions")
.setting(clap::AppSettings::SubcommandRequiredElseHelp)
.subcommand(SubCommand::with_name("management").about("management app")),
)
// inherited from lpc55-host
.subcommand(
SubCommand::with_name("provision")
// .version(crate_version!())
// .long_version(LONG_VERSION.as_str())
.about("Run a sequence of bootloader commands defined in the config file.")
.arg(
Arg::with_name("CONFIG")
.help("Configuration file containing settings")
.required(true),
),
)
.subcommand(
SubCommand::with_name("reboot")
.version(crate_version!())
.long_version(LONG_VERSION.as_str())
.about("reboot device"),
);
cli
}
/// Return the "long" format of lpc55's version string.
///
/// If a revision hash is given, then it is used. If one isn't given, then
/// the LPC55_BUILD_GIT_HASH env var is inspected for it. If that isn't set,
/// then a revision hash is not included in the version string returned.
pub fn long_version(revision_hash: Option<&str>) -> String {
// Do we have a git hash?
// (Yes, if ripgrep was built on a machine with `git` installed.)
let hash = match revision_hash.or(option_env!("LPC55_BUILD_GIT_HASH")) {
None => String::new(),
Some(githash) => format!(" (rev {})", githash),
};
format!("{}{}", crate_version!(), hash)
}
+48
View File
@@ -0,0 +1,48 @@
#[macro_use]
extern crate log;
mod cli;
use core::convert::TryFrom;
use solo2;
fn main() {
pretty_env_logger::init_custom_env("SOLO2_LOG");
info!("solo2 CLI startup");
let args = cli::cli().get_matches();
if let Err(err) = try_main(args) {
eprintln!("Error: {}", err);
std::process::exit(1);
}
}
fn try_main(args: clap::ArgMatches<'_>) -> anyhow::Result<()> {
if let Some(args) = args.subcommand_matches("app") {
use solo2::apps::App;
if let Some(args) = args.subcommand_matches("management") {
info!("interacting with management app");
let mut app = solo2::apps::management::App::new()?;
let answer_to_select = app.select()?;
info!("answer to select: {}", &hex::encode(answer_to_select));
}
}
if let Some(command) = args.subcommand_matches("provision") {
let config_filename = command.value_of("CONFIG").unwrap();
let config = lpc55::bootloader::provision::Config::try_from(config_filename)?;
// let bootloader = bootloader()?;
// for cmd in config.provisions {
// println!("cmd: {:?}", cmd);
// bootloader.run_command(cmd)?;
// }
return Ok(());
}
Ok(())
}
+2
View File
@@ -0,0 +1,2 @@
pub type Error = anyhow::Error;
pub type Result<T> = anyhow::Result<T>;
+45
View File
@@ -0,0 +1,45 @@
pub struct Card {
card: pcsc::Card,
}
impl From<pcsc::Card> for Card {
fn from(card: pcsc::Card) -> Self {
Self { card }
}
}
impl Card {
pub fn call(
&mut self,
// cla: Into<u8>, ins: Into<u8>,
// p1: Into<u8>, p2: Into<u8>,
cla: u8,
ins: u8,
p1: u8,
p2: u8,
data: Option<&[u8]>,
// ) -> iso7816::Result<Vec<u8>> {
) -> crate::Result<Vec<u8>> {
let data = data.unwrap_or(&[]);
let mut send_buffer = Vec::<u8>::with_capacity(data.len() + 16);
send_buffer.push(cla);
send_buffer.push(ins);
send_buffer.push(p1);
send_buffer.push(p2);
// TODO: checks, chain, ...
if data.len() > 0 {
send_buffer.push(data.len() as u8);
send_buffer.extend_from_slice(data);
}
let mut recv_buffer = Vec::<u8>::with_capacity(3072);
recv_buffer.resize(3072, 0);
let l = self.card.transmit(&send_buffer, &mut recv_buffer)?.len();
recv_buffer.resize(l, 0);
Ok(recv_buffer)
}
}
+8
View File
@@ -0,0 +1,8 @@
#[macro_use]
extern crate log;
pub mod apps;
pub mod error;
pub use error::{Error, Result};
pub mod iccd;
pub use iccd::Card;