From 9e9cddc2254e9a5ef0e6a5f81c984e18053f4983 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Mon, 29 Nov 2021 18:32:26 +0100 Subject: [PATCH] Implement setting of 'identity' for NitroKey Start. --- openpgp-card/src/apdu/commands.rs | 5 +++++ openpgp-card/src/card_app.rs | 24 +++++++++++++++++++++++- openpgp-card/src/errors.rs | 3 +++ openpgp-card/src/lib.rs | 6 +++++- pcsc/src/lib.rs | 24 +++++++++++++++++------- scdc/src/lib.rs | 12 ++++++------ tools/src/bin/opgpcard/cli.rs | 7 +++++++ tools/src/bin/opgpcard/main.rs | 14 ++++++++++++++ 8 files changed, 80 insertions(+), 15 deletions(-) diff --git a/openpgp-card/src/apdu/commands.rs b/openpgp-card/src/apdu/commands.rs index b352e8d..100818f 100644 --- a/openpgp-card/src/apdu/commands.rs +++ b/openpgp-card/src/apdu/commands.rs @@ -71,6 +71,11 @@ pub(crate) fn get_firmware_version() -> Command { 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 pub(crate) fn get_response() -> Command { Command::new(0x00, 0xC0, 0x00, 0x00, vec![]) diff --git a/openpgp-card/src/card_app.rs b/openpgp-card/src/card_app.rs index 9ea262d..b18aba4 100644 --- a/openpgp-card/src/card_app.rs +++ b/openpgp-card/src/card_app.rs @@ -18,7 +18,9 @@ use crate::crypto_data::{ CardUploadableKey, Cryptogram, Hash, PublicKeyMaterial, }; 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}; /// Low-level access to OpenPGP card functionality. @@ -244,6 +246,26 @@ impl CardApp { 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> { + 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" /// (e.g. for cardholder certificate) pub fn select_data( diff --git a/openpgp-card/src/errors.rs b/openpgp-card/src/errors.rs index ff1cfbe..b740bd2 100644 --- a/openpgp-card/src/errors.rs +++ b/openpgp-card/src/errors.rs @@ -179,6 +179,9 @@ pub enum SmartcardError { #[error("Failed to connect to the card: {0}")] SmartCardConnectionError(String), + #[error("NotTransacted (SCARD_E_NOT_TRANSACTED)")] + NotTransacted, + #[error("Generic SmartCard Error: {0}")] Error(String), } diff --git a/openpgp-card/src/lib.rs b/openpgp-card/src/lib.rs index ae036b7..8618d8c 100644 --- a/openpgp-card/src/lib.rs +++ b/openpgp-card/src/lib.rs @@ -51,7 +51,11 @@ pub trait CardClient { /// /// `buf_size` is a hint to the backend (the backend may ignore it) /// indicating the expected maximum response size. - fn transmit(&mut self, cmd: &[u8], buf_size: usize) -> Result>; + fn transmit( + &mut self, + cmd: &[u8], + buf_size: usize, + ) -> Result, Error>; /// Set the card capabilities in the CardClient. /// diff --git a/pcsc/src/lib.rs b/pcsc/src/lib.rs index 93de8eb..c70092d 100644 --- a/pcsc/src/lib.rs +++ b/pcsc/src/lib.rs @@ -136,15 +136,25 @@ impl PcscClient { } impl CardClient for PcscClient { - fn transmit(&mut self, cmd: &[u8], buf_size: usize) -> Result> { + fn transmit( + &mut self, + cmd: &[u8], + buf_size: usize, + ) -> Result, Error> { let mut resp_buffer = vec![0; buf_size]; - let resp = self.card.transmit(cmd, &mut resp_buffer).map_err(|e| { - Error::Smartcard(SmartcardError::Error(format!( - "Transmit failed: {:?}", - e - ))) - })?; + let resp = + self.card.transmit(cmd, &mut resp_buffer).map_err( + |e| match e { + pcsc::Error::NotTransacted => { + Error::Smartcard(SmartcardError::NotTransacted) + } + _ => Error::Smartcard(SmartcardError::Error(format!( + "Transmit failed: {:?}", + e + ))), + }, + )?; log::debug!(" <- APDU response: {:x?}", resp); diff --git a/scdc/src/lib.rs b/scdc/src/lib.rs index d4d531d..f9ab60e 100644 --- a/scdc/src/lib.rs +++ b/scdc/src/lib.rs @@ -194,7 +194,7 @@ impl ScdClient { } impl CardClient for ScdClient { - fn transmit(&mut self, cmd: &[u8], _: usize) -> Result> { + fn transmit(&mut self, cmd: &[u8], _: usize) -> Result, Error> { log::trace!("SCDC cmd len {}", cmd.len()); let hex = hex::encode(cmd); @@ -215,10 +215,10 @@ impl CardClient for ScdClient { log::debug!("SCDC command: '{}'", send); if send.len() > ASSUAN_LINELENGTH { - return Err(anyhow!( + return Err(Error::InternalError(anyhow!( "APDU command is too long ({}) to send via Assuan", send.len() - )); + ))); } self.agent.send(send)?; @@ -228,10 +228,10 @@ impl CardClient for ScdClient { while let Some(response) = rt.block_on(self.agent.next()) { log::debug!("res: {:x?}", response); if response.is_err() { - return Err(anyhow!( + return Err(Error::InternalError(anyhow!( "Unexpected error response from SCD {:?}", 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) { diff --git a/tools/src/bin/opgpcard/cli.rs b/tools/src/bin/opgpcard/cli.rs index 09d7862..20cd001 100644 --- a/tools/src/bin/opgpcard/cli.rs +++ b/tools/src/bin/opgpcard/cli.rs @@ -31,6 +31,13 @@ pub enum Command { #[structopt(name = "card ident", short = "c", long = "card")] ident: String, }, + SetIdentity { + #[structopt(name = "card ident", short = "c", long = "card")] + ident: String, + + #[structopt(name = "identity")] + id: u8, + }, Admin { #[structopt(name = "card ident", short = "c", long = "card")] ident: String, diff --git a/tools/src/bin/opgpcard/main.rs b/tools/src/bin/opgpcard/main.rs index 89b96ac..31e1aeb 100644 --- a/tools/src/bin/opgpcard/main.rs +++ b/tools/src/bin/opgpcard/main.rs @@ -34,6 +34,9 @@ fn main() -> Result<(), Box> { cli::Command::Status { ident, verbose } => { print_status(ident, verbose)?; } + cli::Command::SetIdentity { ident, id } => { + set_identity(&ident, id)?; + } cli::Command::Decrypt { ident, user_pin, @@ -147,6 +150,17 @@ fn list_cards() -> Result<()> { Ok(()) } +fn set_identity( + ident: &str, + id: u8, +) -> Result<(), Box> { + let mut card = util::open_card(ident)?; + + card.set_identity(id)?; + + Ok(()) +} + fn print_status(ident: Option, verbose: bool) -> Result<()> { let mut ca = if let Some(ident) = ident { util::open_card(&ident)?