Implement setting of 'identity' for NitroKey Start.

This commit is contained in:
Heiko Schaefer 2021-11-29 18:32:26 +01:00
parent 9de79477b9
commit 9e9cddc225
No known key found for this signature in database
GPG key ID: 4A849A1904CCBD7D
8 changed files with 80 additions and 15 deletions

View file

@ -71,6 +71,11 @@ pub(crate) fn get_firmware_version() -> Command {
Command::new(0x00, 0xF1, 0x00, 0x00, vec![]) Command::new(0x00, 0xF1, 0x00, 0x00, vec![])
} }
/// Set identity [0-2] (NitroKey Start specific(?))
pub(crate) fn set_identity(id: u8) -> Command {
Command::new(0x00, 0x85, 0x00, id, vec![])
}
/// GET RESPONSE /// GET RESPONSE
pub(crate) fn get_response() -> Command { pub(crate) fn get_response() -> Command {
Command::new(0x00, 0xC0, 0x00, 0x00, vec![]) Command::new(0x00, 0xC0, 0x00, 0x00, vec![])

View file

@ -18,7 +18,9 @@ use crate::crypto_data::{
CardUploadableKey, Cryptogram, Hash, PublicKeyMaterial, CardUploadableKey, Cryptogram, Hash, PublicKeyMaterial,
}; };
use crate::tlv::{tag::Tag, value::Value, Tlv}; use crate::tlv::{tag::Tag, value::Value, Tlv};
use crate::{apdu, keys, CardCaps, CardClient, CardClientBox, KeyType}; use crate::{
apdu, keys, CardCaps, CardClient, CardClientBox, KeyType, SmartcardError,
};
use crate::{Error, StatusBytes}; use crate::{Error, StatusBytes};
/// Low-level access to OpenPGP card functionality. /// Low-level access to OpenPGP card functionality.
@ -244,6 +246,26 @@ impl CardApp {
Ok(resp.data()?.into()) Ok(resp.data()?.into())
} }
/// Set identity (Nitrokey Start specific (?)).
/// [see:
/// https://docs.nitrokey.com/start/linux/multiple-identities.html
/// https://github.com/Nitrokey/nitrokey-start-firmware/pull/33/]
pub fn set_identity(&mut self, id: u8) -> Result<Vec<u8>> {
let resp = apdu::send_command(
self.card_client(),
commands::set_identity(id),
false,
);
// Apparently it's normal to get "NotTransacted" from pcsclite when
// the identity switch was successful.
if let Err(Error::Smartcard(SmartcardError::NotTransacted)) = resp {
Ok(vec![])
} else {
Ok(resp?.data()?.into())
}
}
/// SELECT DATA "select a DO in the current template" /// SELECT DATA "select a DO in the current template"
/// (e.g. for cardholder certificate) /// (e.g. for cardholder certificate)
pub fn select_data( pub fn select_data(

View file

@ -179,6 +179,9 @@ pub enum SmartcardError {
#[error("Failed to connect to the card: {0}")] #[error("Failed to connect to the card: {0}")]
SmartCardConnectionError(String), SmartCardConnectionError(String),
#[error("NotTransacted (SCARD_E_NOT_TRANSACTED)")]
NotTransacted,
#[error("Generic SmartCard Error: {0}")] #[error("Generic SmartCard Error: {0}")]
Error(String), Error(String),
} }

View file

@ -51,7 +51,11 @@ pub trait CardClient {
/// ///
/// `buf_size` is a hint to the backend (the backend may ignore it) /// `buf_size` is a hint to the backend (the backend may ignore it)
/// indicating the expected maximum response size. /// indicating the expected maximum response size.
fn transmit(&mut self, cmd: &[u8], buf_size: usize) -> Result<Vec<u8>>; fn transmit(
&mut self,
cmd: &[u8],
buf_size: usize,
) -> Result<Vec<u8>, Error>;
/// Set the card capabilities in the CardClient. /// Set the card capabilities in the CardClient.
/// ///

View file

@ -136,15 +136,25 @@ impl PcscClient {
} }
impl CardClient for PcscClient { impl CardClient for PcscClient {
fn transmit(&mut self, cmd: &[u8], buf_size: usize) -> Result<Vec<u8>> { fn transmit(
&mut self,
cmd: &[u8],
buf_size: usize,
) -> Result<Vec<u8>, Error> {
let mut resp_buffer = vec![0; buf_size]; let mut resp_buffer = vec![0; buf_size];
let resp = self.card.transmit(cmd, &mut resp_buffer).map_err(|e| { let resp =
Error::Smartcard(SmartcardError::Error(format!( self.card.transmit(cmd, &mut resp_buffer).map_err(
"Transmit failed: {:?}", |e| match e {
e pcsc::Error::NotTransacted => {
))) Error::Smartcard(SmartcardError::NotTransacted)
})?; }
_ => Error::Smartcard(SmartcardError::Error(format!(
"Transmit failed: {:?}",
e
))),
},
)?;
log::debug!(" <- APDU response: {:x?}", resp); log::debug!(" <- APDU response: {:x?}", resp);

View file

@ -194,7 +194,7 @@ impl ScdClient {
} }
impl CardClient for ScdClient { impl CardClient for ScdClient {
fn transmit(&mut self, cmd: &[u8], _: usize) -> Result<Vec<u8>> { fn transmit(&mut self, cmd: &[u8], _: usize) -> Result<Vec<u8>, Error> {
log::trace!("SCDC cmd len {}", cmd.len()); log::trace!("SCDC cmd len {}", cmd.len());
let hex = hex::encode(cmd); let hex = hex::encode(cmd);
@ -215,10 +215,10 @@ impl CardClient for ScdClient {
log::debug!("SCDC command: '{}'", send); log::debug!("SCDC command: '{}'", send);
if send.len() > ASSUAN_LINELENGTH { if send.len() > ASSUAN_LINELENGTH {
return Err(anyhow!( return Err(Error::InternalError(anyhow!(
"APDU command is too long ({}) to send via Assuan", "APDU command is too long ({}) to send via Assuan",
send.len() send.len()
)); )));
} }
self.agent.send(send)?; self.agent.send(send)?;
@ -228,10 +228,10 @@ impl CardClient for ScdClient {
while let Some(response) = rt.block_on(self.agent.next()) { while let Some(response) = rt.block_on(self.agent.next()) {
log::debug!("res: {:x?}", response); log::debug!("res: {:x?}", response);
if response.is_err() { if response.is_err() {
return Err(anyhow!( return Err(Error::InternalError(anyhow!(
"Unexpected error response from SCD {:?}", "Unexpected error response from SCD {:?}",
response response
)); )));
} }
if let Ok(Response::Data { partial }) = response { if let Ok(Response::Data { partial }) = response {
@ -246,7 +246,7 @@ impl CardClient for ScdClient {
} }
} }
Err(anyhow!("no response found")) Err(Error::InternalError(anyhow!("no response found")))
} }
fn init_caps(&mut self, caps: CardCaps) { fn init_caps(&mut self, caps: CardCaps) {

View file

@ -31,6 +31,13 @@ pub enum Command {
#[structopt(name = "card ident", short = "c", long = "card")] #[structopt(name = "card ident", short = "c", long = "card")]
ident: String, ident: String,
}, },
SetIdentity {
#[structopt(name = "card ident", short = "c", long = "card")]
ident: String,
#[structopt(name = "identity")]
id: u8,
},
Admin { Admin {
#[structopt(name = "card ident", short = "c", long = "card")] #[structopt(name = "card ident", short = "c", long = "card")]
ident: String, ident: String,

View file

@ -34,6 +34,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
cli::Command::Status { ident, verbose } => { cli::Command::Status { ident, verbose } => {
print_status(ident, verbose)?; print_status(ident, verbose)?;
} }
cli::Command::SetIdentity { ident, id } => {
set_identity(&ident, id)?;
}
cli::Command::Decrypt { cli::Command::Decrypt {
ident, ident,
user_pin, user_pin,
@ -147,6 +150,17 @@ fn list_cards() -> Result<()> {
Ok(()) Ok(())
} }
fn set_identity(
ident: &str,
id: u8,
) -> Result<(), Box<dyn std::error::Error>> {
let mut card = util::open_card(ident)?;
card.set_identity(id)?;
Ok(())
}
fn print_status(ident: Option<String>, verbose: bool) -> Result<()> { fn print_status(ident: Option<String>, verbose: bool) -> Result<()> {
let mut ca = if let Some(ident) = ident { let mut ca = if let Some(ident) = ident {
util::open_card(&ident)? util::open_card(&ident)?