Break out low-level OpenPGP card functionality into card_app.rs
This split makes it possible to write tests based on the low-level API to explore the behaviour of cards in detail.
This commit is contained in:
parent
dc89010869
commit
d1531de4f7
4 changed files with 652 additions and 317 deletions
|
@ -11,7 +11,7 @@ use std::convert::TryFrom;
|
||||||
use crate::apdu::command::Command;
|
use crate::apdu::command::Command;
|
||||||
use crate::apdu::response::Response;
|
use crate::apdu::response::Response;
|
||||||
use crate::errors::{OcErrorStatus, OpenpgpCardError, SmartcardError};
|
use crate::errors::{OcErrorStatus, OpenpgpCardError, SmartcardError};
|
||||||
use crate::CardBase;
|
use crate::CardCaps;
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq)]
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
pub(crate) enum Le {
|
pub(crate) enum Le {
|
||||||
|
@ -27,11 +27,15 @@ pub(crate) enum Le {
|
||||||
pub(crate) fn send_command(
|
pub(crate) fn send_command(
|
||||||
card: &Card,
|
card: &Card,
|
||||||
cmd: Command,
|
cmd: Command,
|
||||||
ext: Le,
|
expect_reply: bool,
|
||||||
oc: Option<&CardBase>,
|
card_caps: Option<&CardCaps>,
|
||||||
) -> Result<Response, OpenpgpCardError> {
|
) -> Result<Response, OpenpgpCardError> {
|
||||||
let mut resp =
|
let mut resp = Response::try_from(send_command_low_level(
|
||||||
Response::try_from(send_command_low_level(&card, cmd, ext, oc)?)?;
|
&card,
|
||||||
|
cmd,
|
||||||
|
expect_reply,
|
||||||
|
card_caps,
|
||||||
|
)?)?;
|
||||||
|
|
||||||
while resp.status()[0] == 0x61 {
|
while resp.status()[0] == 0x61 {
|
||||||
// More data is available for this command from the card
|
// More data is available for this command from the card
|
||||||
|
@ -42,8 +46,8 @@ pub(crate) fn send_command(
|
||||||
let next = Response::try_from(send_command_low_level(
|
let next = Response::try_from(send_command_low_level(
|
||||||
&card,
|
&card,
|
||||||
commands::get_response(),
|
commands::get_response(),
|
||||||
ext,
|
expect_reply,
|
||||||
oc,
|
card_caps,
|
||||||
)?)?;
|
)?)?;
|
||||||
|
|
||||||
// FIXME: first check for 0x61xx or 0x9000?
|
// FIXME: first check for 0x61xx or 0x9000?
|
||||||
|
@ -67,45 +71,45 @@ pub(crate) fn send_command(
|
||||||
fn send_command_low_level(
|
fn send_command_low_level(
|
||||||
card: &Card,
|
card: &Card,
|
||||||
cmd: Command,
|
cmd: Command,
|
||||||
ext: Le,
|
expect_reply: bool,
|
||||||
oc: Option<&CardBase>,
|
card_caps: Option<&CardCaps>,
|
||||||
) -> Result<Vec<u8>, OpenpgpCardError> {
|
) -> Result<Vec<u8>, OpenpgpCardError> {
|
||||||
log::trace!(" -> full APDU command: {:x?}", cmd);
|
let (ext_support, chaining_support, max_cmd_bytes) =
|
||||||
log::trace!(" serialized: {:x?}", cmd.serialize(ext));
|
if let Some(caps) = card_caps {
|
||||||
|
log::trace!("found card caps data!");
|
||||||
|
|
||||||
// default settings
|
(
|
||||||
let mut ext_support = false;
|
caps.ext_support,
|
||||||
let mut chaining_support = false;
|
caps.chaining_support,
|
||||||
let mut chunk_size = 255;
|
caps.max_cmd_bytes as usize,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
log::trace!("found NO card caps data!");
|
||||||
|
|
||||||
// Get feature configuration from card metadata
|
// default settings
|
||||||
if let Some(oc) = oc {
|
(false, false, 255)
|
||||||
if let Ok(hist) = oc.get_historical() {
|
};
|
||||||
if let Some(cc) = hist.get_card_capabilities() {
|
|
||||||
chaining_support = cc.get_command_chaining();
|
|
||||||
ext_support = cc.get_extended_lc_le();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Ok(Some(eli)) = oc.get_extended_length_information() {
|
|
||||||
chunk_size = eli.max_command_bytes as usize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log::trace!(
|
log::trace!(
|
||||||
"ext le/lc {}, chaining {}, command chunk size {}",
|
"ext le/lc {}, chaining {}, command chunk size {}",
|
||||||
ext_support,
|
ext_support,
|
||||||
chaining_support,
|
chaining_support,
|
||||||
chunk_size
|
max_cmd_bytes
|
||||||
);
|
);
|
||||||
|
|
||||||
// update Le setting to 'long', if we're using a larger chunk size
|
// Set Le to 'long', if we're using an extended chunk size.
|
||||||
let ext = match (ext, chunk_size > 0xff) {
|
//
|
||||||
(Le::None, _) => Le::None,
|
// According to the Card spec 3.4.1, pg 47,
|
||||||
|
// 255 is the maximum value for 'Lc' in short mode:
|
||||||
|
// "A short Lc field consists of one byte not set to '00' (1 to 255 dec.)"
|
||||||
|
let ext = match (expect_reply, ext_support && max_cmd_bytes > 0xFF) {
|
||||||
|
(false, _) => Le::None,
|
||||||
(_, true) => Le::Long,
|
(_, true) => Le::Long,
|
||||||
_ => ext,
|
_ => Le::Short,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
log::trace!(" -> full APDU command: {:x?}", cmd);
|
||||||
|
|
||||||
let buf_size = if !ext_support {
|
let buf_size = if !ext_support {
|
||||||
pcsc::MAX_BUFFER_SIZE
|
pcsc::MAX_BUFFER_SIZE
|
||||||
} else {
|
} else {
|
||||||
|
@ -120,7 +124,7 @@ fn send_command_low_level(
|
||||||
log::trace!("chained command mode");
|
log::trace!("chained command mode");
|
||||||
|
|
||||||
// Break up payload into chunks that fit into one command, each
|
// Break up payload into chunks that fit into one command, each
|
||||||
let chunks: Vec<_> = cmd.data.chunks(chunk_size).collect();
|
let chunks: Vec<_> = cmd.data.chunks(max_cmd_bytes).collect();
|
||||||
|
|
||||||
for (i, d) in chunks.iter().enumerate() {
|
for (i, d) in chunks.iter().enumerate() {
|
||||||
let last = i == chunks.len() - 1;
|
let last = i == chunks.len() - 1;
|
||||||
|
|
523
openpgp-card/src/card_app.rs
Normal file
523
openpgp-card/src/card_app.rs
Normal file
|
@ -0,0 +1,523 @@
|
||||||
|
// SPDX-FileCopyrightText: 2021 Heiko Schaefer <heiko@schaefer.name>
|
||||||
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
|
|
||||||
|
//! Direct, low-level, access to OpenPGP card functionality.
|
||||||
|
//!
|
||||||
|
//! No checks are performed here (e.g. for valid data lengths).
|
||||||
|
//! Such checks should be performed on a higher layer, if needed.
|
||||||
|
//!
|
||||||
|
//! Also, no caching of data is done here. If necessary, caching should
|
||||||
|
//! be done on a higher layer.
|
||||||
|
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use pcsc::*;
|
||||||
|
|
||||||
|
use apdu::{commands, response::Response};
|
||||||
|
use parse::{
|
||||||
|
algo_attrs::Algo, algo_info::AlgoInfo, application_id::ApplicationId,
|
||||||
|
cardholder::CardHolder, extended_cap::ExtendedCap,
|
||||||
|
extended_length_info::ExtendedLengthInfo, fingerprint,
|
||||||
|
historical::Historical, pw_status::PWStatus, KeySet,
|
||||||
|
};
|
||||||
|
use tlv::Tlv;
|
||||||
|
|
||||||
|
use crate::errors::OpenpgpCardError;
|
||||||
|
use crate::tlv::tag::Tag;
|
||||||
|
use crate::tlv::TlvEntry;
|
||||||
|
|
||||||
|
use crate::Hash;
|
||||||
|
use crate::{
|
||||||
|
apdu, key_upload, parse, tlv, CardCaps, CardUploadableKey, DecryptMe,
|
||||||
|
KeyType, Sex,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub(crate) struct CardApp {
|
||||||
|
card: Card,
|
||||||
|
card_caps: Option<CardCaps>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CardApp {
|
||||||
|
pub fn new(card: Card) -> Self {
|
||||||
|
Self {
|
||||||
|
card,
|
||||||
|
card_caps: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_caps(self, card_caps: CardCaps) -> Self {
|
||||||
|
Self {
|
||||||
|
card: self.card,
|
||||||
|
card_caps: Some(card_caps),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn card(&self) -> &Card {
|
||||||
|
&self.card
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn card_caps(&self) -> Option<&CardCaps> {
|
||||||
|
self.card_caps.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- application data ---
|
||||||
|
|
||||||
|
/// Load "application related data".
|
||||||
|
///
|
||||||
|
/// This is done once, after opening the OpenPGP card applet
|
||||||
|
/// (the data is stored in the OpenPGPCard object).
|
||||||
|
pub fn get_app_data(&self) -> Result<Tlv> {
|
||||||
|
let ad = commands::get_application_data();
|
||||||
|
let resp = apdu::send_command(&self.card, ad, true, None)?;
|
||||||
|
let entry = TlvEntry::from(resp.data()?, true)?;
|
||||||
|
|
||||||
|
log::trace!(" App data TlvEntry: {:x?}", entry);
|
||||||
|
|
||||||
|
Ok(Tlv(Tag::from([0x6E]), entry))
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- pieces of application related data ---
|
||||||
|
|
||||||
|
pub fn get_aid(ard: &Tlv) -> Result<ApplicationId, OpenpgpCardError> {
|
||||||
|
// get from cached "application related data"
|
||||||
|
let aid = ard.find(&Tag::from([0x4F]));
|
||||||
|
|
||||||
|
if let Some(aid) = aid {
|
||||||
|
Ok(ApplicationId::try_from(&aid.serialize()[..])?)
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("Couldn't get Application ID.").into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_historical(ard: &Tlv) -> Result<Historical, OpenpgpCardError> {
|
||||||
|
// get from cached "application related data"
|
||||||
|
let hist = ard.find(&Tag::from([0x5F, 0x52]));
|
||||||
|
|
||||||
|
if let Some(hist) = hist {
|
||||||
|
log::debug!("Historical bytes: {:x?}", hist);
|
||||||
|
Historical::from(&hist.serialize())
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("Failed to get historical bytes.").into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_extended_length_information(
|
||||||
|
ard: &Tlv,
|
||||||
|
) -> Result<Option<ExtendedLengthInfo>> {
|
||||||
|
// get from cached "application related data"
|
||||||
|
let eli = ard.find(&Tag::from([0x7F, 0x66]));
|
||||||
|
|
||||||
|
log::debug!("Extended length information: {:x?}", eli);
|
||||||
|
|
||||||
|
if let Some(eli) = eli {
|
||||||
|
// The card has returned extended length information
|
||||||
|
Ok(Some(ExtendedLengthInfo::from(&eli.serialize()[..])?))
|
||||||
|
} else {
|
||||||
|
// The card didn't return this (optional) DO. That is ok.
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_general_feature_management() -> Option<bool> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_discretionary_data_objects() {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_extended_capabilities(
|
||||||
|
ard: &Tlv,
|
||||||
|
) -> Result<ExtendedCap, OpenpgpCardError> {
|
||||||
|
// get from cached "application related data"
|
||||||
|
let ecap = ard.find(&Tag::from([0xc0]));
|
||||||
|
|
||||||
|
if let Some(ecap) = ecap {
|
||||||
|
Ok(ExtendedCap::try_from(&ecap.serialize()[..])?)
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("Failed to get extended capabilities.").into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_algorithm_attributes(
|
||||||
|
ard: &Tlv,
|
||||||
|
key_type: KeyType,
|
||||||
|
) -> Result<Algo> {
|
||||||
|
// get from cached "application related data"
|
||||||
|
let aa = ard.find(&Tag::from([key_type.get_algorithm_tag()]));
|
||||||
|
|
||||||
|
if let Some(aa) = aa {
|
||||||
|
Algo::try_from(&aa.serialize()[..])
|
||||||
|
} else {
|
||||||
|
Err(anyhow!(
|
||||||
|
"Failed to get algorithm attributes for {:?}.",
|
||||||
|
key_type
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// PW status Bytes
|
||||||
|
pub fn get_pw_status_bytes(ard: &Tlv) -> Result<PWStatus> {
|
||||||
|
// get from cached "application related data"
|
||||||
|
let psb = ard.find(&Tag::from([0xc4]));
|
||||||
|
|
||||||
|
if let Some(psb) = psb {
|
||||||
|
let pws = PWStatus::try_from(&psb.serialize())?;
|
||||||
|
|
||||||
|
log::debug!("PW Status: {:x?}", pws);
|
||||||
|
|
||||||
|
Ok(pws)
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("Failed to get PW status Bytes."))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_fingerprints(
|
||||||
|
ard: &Tlv,
|
||||||
|
) -> Result<KeySet<fingerprint::Fingerprint>, OpenpgpCardError> {
|
||||||
|
// Get from cached "application related data"
|
||||||
|
let fp = ard.find(&Tag::from([0xc5]));
|
||||||
|
|
||||||
|
if let Some(fp) = fp {
|
||||||
|
let fp = fingerprint::from(&fp.serialize())?;
|
||||||
|
|
||||||
|
log::debug!("Fp: {:x?}", fp);
|
||||||
|
|
||||||
|
Ok(fp)
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("Failed to get fingerprints.").into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---
|
||||||
|
|
||||||
|
pub fn get_ca_fingerprints() {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_key_generation_times() {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_key_information() {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_uif_pso_cds() {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_uif_pso_dec() {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_uif_pso_aut() {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
pub fn get_uif_attestation() {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- optional private DOs (0101 - 0104) ---
|
||||||
|
|
||||||
|
// --- login data (5e) ---
|
||||||
|
|
||||||
|
// --- URL (5f50) ---
|
||||||
|
|
||||||
|
pub fn get_url(&self) -> Result<String> {
|
||||||
|
let resp = apdu::send_command(
|
||||||
|
&self.card,
|
||||||
|
commands::get_url(),
|
||||||
|
true,
|
||||||
|
self.card_caps.as_ref(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(String::from_utf8_lossy(resp.data()?).to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- cardholder related data (65) ---
|
||||||
|
pub fn get_cardholder_related_data(&self) -> Result<CardHolder> {
|
||||||
|
let crd = commands::cardholder_related_data();
|
||||||
|
let resp = apdu::send_command(
|
||||||
|
&self.card,
|
||||||
|
crd,
|
||||||
|
true,
|
||||||
|
self.card_caps.as_ref(),
|
||||||
|
)?;
|
||||||
|
resp.check_ok()?;
|
||||||
|
|
||||||
|
CardHolder::try_from(resp.data()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- security support template (7a) ---
|
||||||
|
pub fn get_security_support_template(&self) -> Result<Tlv> {
|
||||||
|
let sst = commands::get_security_support_template();
|
||||||
|
let resp = apdu::send_command(
|
||||||
|
&self.card,
|
||||||
|
sst,
|
||||||
|
true,
|
||||||
|
self.card_caps.as_ref(),
|
||||||
|
)?;
|
||||||
|
resp.check_ok()?;
|
||||||
|
|
||||||
|
Tlv::try_from(resp.data()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DO "Algorithm Information" (0xFA)
|
||||||
|
pub fn list_supported_algo(&self) -> Result<Option<AlgoInfo>> {
|
||||||
|
let resp = apdu::send_command(
|
||||||
|
&self.card,
|
||||||
|
commands::get_algo_list(),
|
||||||
|
true,
|
||||||
|
self.card_caps.as_ref(),
|
||||||
|
)?;
|
||||||
|
resp.check_ok()?;
|
||||||
|
|
||||||
|
let ai = AlgoInfo::try_from(resp.data()?)?;
|
||||||
|
Ok(Some(ai))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------
|
||||||
|
|
||||||
|
/// Delete all state on this OpenPGP card
|
||||||
|
pub fn factory_reset(&self) -> Result<()> {
|
||||||
|
// send 4 bad requests to verify pw1
|
||||||
|
// [apdu 00 20 00 81 08 40 40 40 40 40 40 40 40]
|
||||||
|
for _ in 0..4 {
|
||||||
|
let verify = commands::verify_pw1_81([0x40; 8].to_vec());
|
||||||
|
let resp = apdu::send_command(
|
||||||
|
&self.card,
|
||||||
|
verify,
|
||||||
|
false,
|
||||||
|
self.card_caps.as_ref(),
|
||||||
|
)?;
|
||||||
|
if !(resp.status() == [0x69, 0x82]
|
||||||
|
|| resp.status() == [0x69, 0x83])
|
||||||
|
{
|
||||||
|
return Err(anyhow!("Unexpected status for reset, at pw1."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// send 4 bad requests to verify pw3
|
||||||
|
// [apdu 00 20 00 83 08 40 40 40 40 40 40 40 40]
|
||||||
|
for _ in 0..4 {
|
||||||
|
let verify = commands::verify_pw3([0x40; 8].to_vec());
|
||||||
|
let resp = apdu::send_command(
|
||||||
|
&self.card,
|
||||||
|
verify,
|
||||||
|
false,
|
||||||
|
self.card_caps.as_ref(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if !(resp.status() == [0x69, 0x82]
|
||||||
|
|| resp.status() == [0x69, 0x83])
|
||||||
|
{
|
||||||
|
return Err(anyhow!("Unexpected status for reset, at pw3."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// terminate_df [apdu 00 e6 00 00]
|
||||||
|
let term = commands::terminate_df();
|
||||||
|
let resp = apdu::send_command(
|
||||||
|
&self.card,
|
||||||
|
term,
|
||||||
|
false,
|
||||||
|
self.card_caps.as_ref(),
|
||||||
|
)?;
|
||||||
|
resp.check_ok()?;
|
||||||
|
|
||||||
|
// activate_file [apdu 00 44 00 00]
|
||||||
|
let act = commands::activate_file();
|
||||||
|
let resp = apdu::send_command(
|
||||||
|
&self.card,
|
||||||
|
act,
|
||||||
|
false,
|
||||||
|
self.card_caps.as_ref(),
|
||||||
|
)?;
|
||||||
|
resp.check_ok()?;
|
||||||
|
|
||||||
|
// FIXME: does the connection need to be re-opened on some cards,
|
||||||
|
// after reset?!
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify_pw1_for_signing(
|
||||||
|
&self,
|
||||||
|
pin: &str,
|
||||||
|
) -> Result<Response, OpenpgpCardError> {
|
||||||
|
assert!(pin.len() >= 6); // FIXME: Err
|
||||||
|
|
||||||
|
let verify = commands::verify_pw1_81(pin.as_bytes().to_vec());
|
||||||
|
apdu::send_command(&self.card, verify, false, self.card_caps.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_pw1(&self) -> Result<Response, OpenpgpCardError> {
|
||||||
|
let verify = commands::verify_pw1_82(vec![]);
|
||||||
|
apdu::send_command(&self.card, verify, false, self.card_caps.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify_pw1(&self, pin: &str) -> Result<Response, OpenpgpCardError> {
|
||||||
|
assert!(pin.len() >= 6); // FIXME: Err
|
||||||
|
|
||||||
|
let verify = commands::verify_pw1_82(pin.as_bytes().to_vec());
|
||||||
|
apdu::send_command(&self.card, verify, false, self.card_caps.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_pw3(&self) -> Result<Response, OpenpgpCardError> {
|
||||||
|
let verify = commands::verify_pw3(vec![]);
|
||||||
|
apdu::send_command(&self.card, verify, false, self.card_caps.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify_pw3(&self, pin: &str) -> Result<Response, OpenpgpCardError> {
|
||||||
|
assert!(pin.len() >= 8); // FIXME: Err
|
||||||
|
|
||||||
|
let verify = commands::verify_pw3(pin.as_bytes().to_vec());
|
||||||
|
apdu::send_command(&self.card, verify, false, self.card_caps.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- decrypt ---
|
||||||
|
|
||||||
|
/// Decrypt the ciphertext in `dm`, on the card.
|
||||||
|
pub fn decrypt(&self, dm: DecryptMe) -> Result<Vec<u8>, OpenpgpCardError> {
|
||||||
|
match dm {
|
||||||
|
DecryptMe::RSA(message) => {
|
||||||
|
let mut data = vec![0x0];
|
||||||
|
data.extend_from_slice(message);
|
||||||
|
|
||||||
|
// Call the card to decrypt `data`
|
||||||
|
self.pso_decipher(data)
|
||||||
|
}
|
||||||
|
DecryptMe::ECDH(eph) => {
|
||||||
|
// External Public Key
|
||||||
|
let epk = Tlv(Tag(vec![0x86]), TlvEntry::S(eph.to_vec()));
|
||||||
|
|
||||||
|
// Public Key DO
|
||||||
|
let pkdo = Tlv(Tag(vec![0x7f, 0x49]), TlvEntry::C(vec![epk]));
|
||||||
|
|
||||||
|
// Cipher DO
|
||||||
|
let cdo = Tlv(Tag(vec![0xa6]), TlvEntry::C(vec![pkdo]));
|
||||||
|
|
||||||
|
self.pso_decipher(cdo.serialize())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run decryption operation on the smartcard
|
||||||
|
/// (7.2.11 PSO: DECIPHER)
|
||||||
|
pub(crate) fn pso_decipher(
|
||||||
|
&self,
|
||||||
|
data: Vec<u8>,
|
||||||
|
) -> Result<Vec<u8>, OpenpgpCardError> {
|
||||||
|
// The OpenPGP card is already connected and PW1 82 has been verified
|
||||||
|
let dec_cmd = commands::decryption(data);
|
||||||
|
let resp = apdu::send_command(
|
||||||
|
&self.card,
|
||||||
|
dec_cmd,
|
||||||
|
true,
|
||||||
|
self.card_caps.as_ref(),
|
||||||
|
)?;
|
||||||
|
resp.check_ok()?;
|
||||||
|
|
||||||
|
Ok(resp.data().map(|d| d.to_vec())?)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- sign ---
|
||||||
|
|
||||||
|
/// Sign the message in `hash`, on the card.
|
||||||
|
pub fn signature_for_hash(
|
||||||
|
&self,
|
||||||
|
hash: Hash,
|
||||||
|
) -> Result<Vec<u8>, OpenpgpCardError> {
|
||||||
|
let data = match hash {
|
||||||
|
Hash::SHA256(_) | Hash::SHA384(_) | Hash::SHA512(_) => {
|
||||||
|
let tlv = Tlv(
|
||||||
|
Tag(vec![0x30]),
|
||||||
|
TlvEntry::C(vec![
|
||||||
|
Tlv(
|
||||||
|
Tag(vec![0x30]),
|
||||||
|
TlvEntry::C(vec![
|
||||||
|
Tlv(
|
||||||
|
Tag(vec![0x06]),
|
||||||
|
// unwrapping is ok, for SHA*
|
||||||
|
TlvEntry::S(hash.oid().unwrap().to_vec()),
|
||||||
|
),
|
||||||
|
Tlv(Tag(vec![0x05]), TlvEntry::S(vec![])),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
Tlv(
|
||||||
|
Tag(vec![0x04]),
|
||||||
|
TlvEntry::S(hash.digest().to_vec()),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
tlv.serialize()
|
||||||
|
}
|
||||||
|
Hash::EdDSA(d) => d.to_vec(),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.compute_digital_signature(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run signing operation on the smartcard
|
||||||
|
/// (7.2.10 PSO: COMPUTE DIGITAL SIGNATURE)
|
||||||
|
pub(crate) fn compute_digital_signature(
|
||||||
|
&self,
|
||||||
|
data: Vec<u8>,
|
||||||
|
) -> Result<Vec<u8>, OpenpgpCardError> {
|
||||||
|
let dec_cmd = commands::signature(data);
|
||||||
|
|
||||||
|
let resp = apdu::send_command(
|
||||||
|
&self.card,
|
||||||
|
dec_cmd,
|
||||||
|
true,
|
||||||
|
self.card_caps.as_ref(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(resp.data().map(|d| d.to_vec())?)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- admin ---
|
||||||
|
|
||||||
|
pub fn set_name(&self, name: &str) -> Result<Response, OpenpgpCardError> {
|
||||||
|
let put_name = commands::put_name(name.as_bytes().to_vec());
|
||||||
|
apdu::send_command(
|
||||||
|
&self.card,
|
||||||
|
put_name,
|
||||||
|
false,
|
||||||
|
self.card_caps.as_ref(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_lang(&self, lang: &str) -> Result<Response, OpenpgpCardError> {
|
||||||
|
let put_lang = commands::put_lang(lang.as_bytes().to_vec());
|
||||||
|
apdu::send_command(
|
||||||
|
&self.card,
|
||||||
|
put_lang,
|
||||||
|
false,
|
||||||
|
self.card_caps.as_ref(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_sex(&self, sex: Sex) -> Result<Response, OpenpgpCardError> {
|
||||||
|
let put_sex = commands::put_sex(sex.as_u8());
|
||||||
|
apdu::send_command(&self.card, put_sex, false, self.card_caps.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_url(&self, url: &str) -> Result<Response, OpenpgpCardError> {
|
||||||
|
let put_url = commands::put_url(url.as_bytes().to_vec());
|
||||||
|
apdu::send_command(&self.card, put_url, false, self.card_caps.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn upload_key(
|
||||||
|
&self,
|
||||||
|
key: Box<dyn CardUploadableKey>,
|
||||||
|
key_type: KeyType,
|
||||||
|
) -> Result<(), OpenpgpCardError> {
|
||||||
|
let algo_list = self.list_supported_algo()?;
|
||||||
|
|
||||||
|
key_upload::upload_key(&self, key, key_type, algo_list)
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,31 +3,29 @@
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
|
|
||||||
use crate::apdu;
|
|
||||||
use crate::apdu::command::Command;
|
use crate::apdu::command::Command;
|
||||||
use crate::apdu::commands;
|
use crate::apdu::commands;
|
||||||
use crate::apdu::Le;
|
use crate::card_app::CardApp;
|
||||||
use crate::errors::OpenpgpCardError;
|
use crate::errors::OpenpgpCardError;
|
||||||
use crate::parse::algo_attrs::{Algo, RsaAttrs};
|
use crate::parse::algo_attrs::{Algo, RsaAttrs};
|
||||||
use crate::parse::algo_info::AlgoInfo;
|
use crate::parse::algo_info::AlgoInfo;
|
||||||
use crate::tlv::{tag::Tag, Tlv, TlvEntry};
|
use crate::tlv::{tag::Tag, Tlv, TlvEntry};
|
||||||
|
use crate::{apdu, CardCaps};
|
||||||
use crate::{
|
use crate::{
|
||||||
tlv, CardAdmin, CardUploadableKey, EccKey, EccType, KeyType,
|
tlv, CardUploadableKey, EccKey, EccType, KeyType, PrivateKeyMaterial,
|
||||||
PrivateKeyMaterial, RSAKey,
|
RSAKey,
|
||||||
};
|
};
|
||||||
|
use pcsc::Card;
|
||||||
|
|
||||||
/// Upload an explicitly selected Key to the card as a specific KeyType.
|
/// Upload an explicitly selected Key to the card as a specific KeyType.
|
||||||
///
|
///
|
||||||
/// The client needs to make sure that the key is suitable for `key_type`.
|
/// The client needs to make sure that the key is suitable for `key_type`.
|
||||||
pub(crate) fn upload_key(
|
pub(crate) fn upload_key(
|
||||||
oca: &CardAdmin,
|
card_app: &CardApp,
|
||||||
key: Box<dyn CardUploadableKey>,
|
key: Box<dyn CardUploadableKey>,
|
||||||
key_type: KeyType,
|
key_type: KeyType,
|
||||||
|
algo_list: Option<AlgoInfo>,
|
||||||
) -> Result<(), OpenpgpCardError> {
|
) -> Result<(), OpenpgpCardError> {
|
||||||
// FIXME: the list of algorithms is retrieved multiple times, it should
|
|
||||||
// be cached
|
|
||||||
let algo_list = oca.list_supported_algo()?;
|
|
||||||
|
|
||||||
let (algo_cmd, key_cmd) = match key.get_key()? {
|
let (algo_cmd, key_cmd) = match key.get_key()? {
|
||||||
PrivateKeyMaterial::R(rsa_key) => {
|
PrivateKeyMaterial::R(rsa_key) => {
|
||||||
// RSA bitsize
|
// RSA bitsize
|
||||||
|
@ -35,7 +33,7 @@ pub(crate) fn upload_key(
|
||||||
let rsa_bits =
|
let rsa_bits =
|
||||||
(((rsa_key.get_n().len() * 8 + 31) / 32) * 32) as u16;
|
(((rsa_key.get_n().len() * 8 + 31) / 32) * 32) as u16;
|
||||||
|
|
||||||
// FIXME: deal with absence of algo list (unwrap!)
|
// FIXME: deal with absence of algo list (don't just unwrap!)
|
||||||
// Get suitable algorithm from card's list
|
// Get suitable algorithm from card's list
|
||||||
let algo =
|
let algo =
|
||||||
get_card_algo_rsa(algo_list.unwrap(), key_type, rsa_bits);
|
get_card_algo_rsa(algo_list.unwrap(), key_type, rsa_bits);
|
||||||
|
@ -71,12 +69,13 @@ pub(crate) fn upload_key(
|
||||||
};
|
};
|
||||||
|
|
||||||
copy_key_to_card(
|
copy_key_to_card(
|
||||||
oca,
|
card_app.card(),
|
||||||
key_type,
|
key_type,
|
||||||
key.get_ts(),
|
key.get_ts(),
|
||||||
key.get_fp(),
|
key.get_fp(),
|
||||||
algo_cmd,
|
algo_cmd,
|
||||||
key_cmd,
|
key_cmd,
|
||||||
|
card_app.card_caps(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -369,12 +368,13 @@ fn ecc_algo_attrs_cmd(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn copy_key_to_card(
|
fn copy_key_to_card(
|
||||||
oca: &CardAdmin,
|
card: &Card,
|
||||||
key_type: KeyType,
|
key_type: KeyType,
|
||||||
ts: u64,
|
ts: u64,
|
||||||
fp: Vec<u8>,
|
fp: Vec<u8>,
|
||||||
algo_cmd: Command,
|
algo_cmd: Command,
|
||||||
key_cmd: Command,
|
key_cmd: Command,
|
||||||
|
card_caps: Option<&CardCaps>,
|
||||||
) -> Result<(), OpenpgpCardError> {
|
) -> Result<(), OpenpgpCardError> {
|
||||||
let fp_cmd = commands::put_data(&[key_type.get_fingerprint_put_tag()], fp);
|
let fp_cmd = commands::put_data(&[key_type.get_fingerprint_put_tag()], fp);
|
||||||
|
|
||||||
|
@ -391,15 +391,13 @@ fn copy_key_to_card(
|
||||||
|
|
||||||
// Send all the commands
|
// Send all the commands
|
||||||
|
|
||||||
let ext = Le::None; // FIXME?!
|
|
||||||
|
|
||||||
// FIXME: Only write algo attributes to the card if "extended
|
// FIXME: Only write algo attributes to the card if "extended
|
||||||
// capabilities" show that they are changeable!
|
// capabilities" show that they are changeable!
|
||||||
apdu::send_command(oca.card(), algo_cmd, ext, Some(oca))?.check_ok()?;
|
apdu::send_command(card, algo_cmd, false, card_caps)?.check_ok()?;
|
||||||
|
|
||||||
apdu::send_command(oca.card(), key_cmd, ext, Some(oca))?.check_ok()?;
|
apdu::send_command(card, key_cmd, false, card_caps)?.check_ok()?;
|
||||||
apdu::send_command(oca.card(), fp_cmd, ext, Some(oca))?.check_ok()?;
|
apdu::send_command(card, fp_cmd, false, card_caps)?.check_ok()?;
|
||||||
apdu::send_command(oca.card(), time_cmd, ext, Some(oca))?.check_ok()?;
|
apdu::send_command(card, time_cmd, false, card_caps)?.check_ok()?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Heiko Schaefer <heiko@schaefer.name>
|
// SPDX-FileCopyrightText: 2021 Heiko Schaefer <heiko@schaefer.name>
|
||||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
|
|
||||||
use std::convert::TryFrom;
|
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use pcsc::*;
|
use pcsc::*;
|
||||||
|
|
||||||
use apdu::{commands, response::Response, Le};
|
use apdu::{commands, response::Response};
|
||||||
use parse::{
|
use parse::{
|
||||||
algo_attrs::Algo, algo_info::AlgoInfo, application_id::ApplicationId,
|
algo_attrs::Algo, algo_info::AlgoInfo, application_id::ApplicationId,
|
||||||
cardholder::CardHolder, extended_cap::ExtendedCap, extended_cap::Features,
|
cardholder::CardHolder, extended_cap::ExtendedCap, extended_cap::Features,
|
||||||
|
@ -15,18 +13,26 @@ use parse::{
|
||||||
};
|
};
|
||||||
use tlv::Tlv;
|
use tlv::Tlv;
|
||||||
|
|
||||||
|
use crate::card_app::CardApp;
|
||||||
use crate::errors::{OpenpgpCardError, SmartcardError};
|
use crate::errors::{OpenpgpCardError, SmartcardError};
|
||||||
use crate::tlv::tag::Tag;
|
|
||||||
use crate::tlv::TlvEntry;
|
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
mod apdu;
|
mod apdu;
|
||||||
mod card;
|
mod card;
|
||||||
|
mod card_app;
|
||||||
pub mod errors;
|
pub mod errors;
|
||||||
mod key_upload;
|
mod key_upload;
|
||||||
mod parse;
|
mod parse;
|
||||||
mod tlv;
|
mod tlv;
|
||||||
|
|
||||||
|
/// Information about the capabilities of the card.
|
||||||
|
/// (feature configuration from card metadata)
|
||||||
|
pub(crate) struct CardCaps {
|
||||||
|
pub(crate) ext_support: bool,
|
||||||
|
pub(crate) chaining_support: bool,
|
||||||
|
pub(crate) max_cmd_bytes: u16,
|
||||||
|
}
|
||||||
|
|
||||||
/// Container for a hash value.
|
/// Container for a hash value.
|
||||||
/// These hash values can be signed by the card.
|
/// These hash values can be signed by the card.
|
||||||
pub enum Hash<'a> {
|
pub enum Hash<'a> {
|
||||||
|
@ -210,7 +216,7 @@ impl KeyType {
|
||||||
/// Representation of an opened OpenPGP card in its basic, freshly opened,
|
/// Representation of an opened OpenPGP card in its basic, freshly opened,
|
||||||
/// state (i.e. no passwords have been verified, default privileges apply).
|
/// state (i.e. no passwords have been verified, default privileges apply).
|
||||||
pub struct CardBase {
|
pub struct CardBase {
|
||||||
card: Card,
|
card_app: CardApp,
|
||||||
|
|
||||||
// Cache of "application related data".
|
// Cache of "application related data".
|
||||||
//
|
//
|
||||||
|
@ -280,14 +286,43 @@ impl CardBase {
|
||||||
/// Open connection to a specific card and select the openpgp applet
|
/// Open connection to a specific card and select the openpgp applet
|
||||||
fn open_card(card: Card) -> Result<Self, OpenpgpCardError> {
|
fn open_card(card: Card) -> Result<Self, OpenpgpCardError> {
|
||||||
let select_openpgp = commands::select_openpgp();
|
let select_openpgp = commands::select_openpgp();
|
||||||
|
let resp = apdu::send_command(&card, select_openpgp, true, None)?;
|
||||||
let resp = apdu::send_command(&card, select_openpgp, Le::Short, None)?;
|
|
||||||
|
|
||||||
if resp.is_ok() {
|
if resp.is_ok() {
|
||||||
// read and cache "application related data"
|
// read and cache "application related data"
|
||||||
let ard = Self::get_app_data(&card)?;
|
let card_app = CardApp::new(card);
|
||||||
|
let ard = card_app.get_app_data()?;
|
||||||
|
|
||||||
Ok(Self { card, ard })
|
// Determine chaining/extended length support from card
|
||||||
|
// metadata and cache this information in CardApp (as a
|
||||||
|
// CardCaps)
|
||||||
|
|
||||||
|
let mut ext_support = false;
|
||||||
|
let mut chaining_support = false;
|
||||||
|
|
||||||
|
if let Ok(hist) = CardApp::get_historical(&ard) {
|
||||||
|
if let Some(cc) = hist.get_card_capabilities() {
|
||||||
|
chaining_support = cc.get_command_chaining();
|
||||||
|
ext_support = cc.get_extended_lc_le();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let max_cmd_bytes = if let Ok(Some(eli)) =
|
||||||
|
CardApp::get_extended_length_information(&ard)
|
||||||
|
{
|
||||||
|
eli.max_command_bytes
|
||||||
|
} else {
|
||||||
|
255
|
||||||
|
};
|
||||||
|
|
||||||
|
let caps = CardCaps {
|
||||||
|
ext_support,
|
||||||
|
chaining_support,
|
||||||
|
max_cmd_bytes,
|
||||||
|
};
|
||||||
|
let card_app = card_app.set_caps(caps);
|
||||||
|
|
||||||
|
Ok(Self { card_app, ard })
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!("Couldn't open OpenPGP application").into())
|
Err(anyhow!("Couldn't open OpenPGP application").into())
|
||||||
}
|
}
|
||||||
|
@ -299,54 +334,22 @@ impl CardBase {
|
||||||
///
|
///
|
||||||
/// This is done once, after opening the OpenPGP card applet
|
/// This is done once, after opening the OpenPGP card applet
|
||||||
/// (the data is stored in the OpenPGPCard object).
|
/// (the data is stored in the OpenPGPCard object).
|
||||||
fn get_app_data(card: &Card) -> Result<Tlv> {
|
fn get_app_data(&self) -> Result<Tlv> {
|
||||||
let ad = commands::get_application_data();
|
self.card_app.get_app_data()
|
||||||
let resp = apdu::send_command(card, ad, Le::Short, None)?;
|
|
||||||
let entry = TlvEntry::from(resp.data()?, true)?;
|
|
||||||
|
|
||||||
log::trace!(" App data TlvEntry: {:x?}", entry);
|
|
||||||
|
|
||||||
Ok(Tlv(Tag::from([0x6E]), entry))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_aid(&self) -> Result<ApplicationId, OpenpgpCardError> {
|
pub fn get_aid(&self) -> Result<ApplicationId, OpenpgpCardError> {
|
||||||
// get from cached "application related data"
|
CardApp::get_aid(&self.ard)
|
||||||
let aid = self.ard.find(&Tag::from([0x4F]));
|
|
||||||
|
|
||||||
if let Some(aid) = aid {
|
|
||||||
Ok(ApplicationId::try_from(&aid.serialize()[..])?)
|
|
||||||
} else {
|
|
||||||
Err(anyhow!("Couldn't get Application ID.").into())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_historical(&self) -> Result<Historical, OpenpgpCardError> {
|
pub fn get_historical(&self) -> Result<Historical, OpenpgpCardError> {
|
||||||
// get from cached "application related data"
|
CardApp::get_historical(&self.ard)
|
||||||
let hist = self.ard.find(&Tag::from([0x5F, 0x52]));
|
|
||||||
|
|
||||||
if let Some(hist) = hist {
|
|
||||||
log::debug!("Historical bytes: {:x?}", hist);
|
|
||||||
Historical::from(&hist.serialize())
|
|
||||||
} else {
|
|
||||||
Err(anyhow!("Failed to get historical bytes.").into())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_extended_length_information(
|
pub fn get_extended_length_information(
|
||||||
&self,
|
&self,
|
||||||
) -> Result<Option<ExtendedLengthInfo>> {
|
) -> Result<Option<ExtendedLengthInfo>> {
|
||||||
// get from cached "application related data"
|
CardApp::get_extended_length_information(&self.ard)
|
||||||
let eli = self.ard.find(&Tag::from([0x7F, 0x66]));
|
|
||||||
|
|
||||||
log::debug!("Extended length information: {:x?}", eli);
|
|
||||||
|
|
||||||
if let Some(eli) = eli {
|
|
||||||
// The card has returned extended length information
|
|
||||||
Ok(Some(ExtendedLengthInfo::from(&eli.serialize()[..])?))
|
|
||||||
} else {
|
|
||||||
// The card didn't return this (optional) DO. That is ok.
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_general_feature_management() -> Option<bool> {
|
pub fn get_general_feature_management() -> Option<bool> {
|
||||||
|
@ -360,61 +363,22 @@ impl CardBase {
|
||||||
pub fn get_extended_capabilities(
|
pub fn get_extended_capabilities(
|
||||||
&self,
|
&self,
|
||||||
) -> Result<ExtendedCap, OpenpgpCardError> {
|
) -> Result<ExtendedCap, OpenpgpCardError> {
|
||||||
// get from cached "application related data"
|
CardApp::get_extended_capabilities(&self.ard)
|
||||||
let ecap = self.ard.find(&Tag::from([0xc0]));
|
|
||||||
|
|
||||||
if let Some(ecap) = ecap {
|
|
||||||
Ok(ExtendedCap::try_from(&ecap.serialize()[..])?)
|
|
||||||
} else {
|
|
||||||
Err(anyhow!("Failed to get extended capabilities.").into())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_algorithm_attributes(&self, key_type: KeyType) -> Result<Algo> {
|
pub fn get_algorithm_attributes(&self, key_type: KeyType) -> Result<Algo> {
|
||||||
// get from cached "application related data"
|
CardApp::get_algorithm_attributes(&self.ard, key_type)
|
||||||
let aa = self.ard.find(&Tag::from([key_type.get_algorithm_tag()]));
|
|
||||||
|
|
||||||
if let Some(aa) = aa {
|
|
||||||
Algo::try_from(&aa.serialize()[..])
|
|
||||||
} else {
|
|
||||||
Err(anyhow!(
|
|
||||||
"Failed to get algorithm attributes for {:?}.",
|
|
||||||
key_type
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// PW status Bytes
|
/// PW status Bytes
|
||||||
pub fn get_pw_status_bytes(&self) -> Result<PWStatus> {
|
pub fn get_pw_status_bytes(&self) -> Result<PWStatus> {
|
||||||
// get from cached "application related data"
|
CardApp::get_pw_status_bytes(&self.ard)
|
||||||
let psb = self.ard.find(&Tag::from([0xc4]));
|
|
||||||
|
|
||||||
if let Some(psb) = psb {
|
|
||||||
let pws = PWStatus::try_from(&psb.serialize())?;
|
|
||||||
|
|
||||||
log::debug!("PW Status: {:x?}", pws);
|
|
||||||
|
|
||||||
Ok(pws)
|
|
||||||
} else {
|
|
||||||
Err(anyhow!("Failed to get PW status Bytes.").into())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_fingerprints(
|
pub fn get_fingerprints(
|
||||||
&self,
|
&self,
|
||||||
) -> Result<KeySet<fingerprint::Fingerprint>, OpenpgpCardError> {
|
) -> Result<KeySet<fingerprint::Fingerprint>, OpenpgpCardError> {
|
||||||
// Get from cached "application related data"
|
CardApp::get_fingerprints(&self.ard)
|
||||||
let fp = self.ard.find(&Tag::from([0xc5]));
|
|
||||||
|
|
||||||
if let Some(fp) = fp {
|
|
||||||
let fp = fingerprint::from(&fp.serialize())?;
|
|
||||||
|
|
||||||
log::debug!("Fp: {:x?}", fp);
|
|
||||||
|
|
||||||
Ok(fp)
|
|
||||||
} else {
|
|
||||||
Err(anyhow!("Failed to get fingerprints.").into())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_ca_fingerprints(&self) {
|
pub fn get_ca_fingerprints(&self) {
|
||||||
|
@ -451,51 +415,17 @@ impl CardBase {
|
||||||
// --- URL (5f50) ---
|
// --- URL (5f50) ---
|
||||||
|
|
||||||
pub fn get_url(&self) -> Result<String> {
|
pub fn get_url(&self) -> Result<String> {
|
||||||
let _eli = self.get_extended_length_information()?;
|
self.card_app.get_url()
|
||||||
|
|
||||||
// FIXME: figure out Le
|
|
||||||
let resp = apdu::send_command(
|
|
||||||
&self.card,
|
|
||||||
commands::get_url(),
|
|
||||||
Le::Long,
|
|
||||||
Some(self),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
log::trace!(
|
|
||||||
" final response: {:x?}, data len {}",
|
|
||||||
resp,
|
|
||||||
resp.raw_data().len()
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(String::from_utf8_lossy(resp.data()?).to_string())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- cardholder related data (65) ---
|
// --- cardholder related data (65) ---
|
||||||
pub fn get_cardholder_related_data(&self) -> Result<CardHolder> {
|
pub fn get_cardholder_related_data(&self) -> Result<CardHolder> {
|
||||||
let crd = commands::cardholder_related_data();
|
self.card_app.get_cardholder_related_data()
|
||||||
let resp = apdu::send_command(&self.card, crd, Le::Short, Some(self))?;
|
|
||||||
resp.check_ok()?;
|
|
||||||
|
|
||||||
CardHolder::try_from(resp.data()?)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- security support template (7a) ---
|
// --- security support template (7a) ---
|
||||||
pub fn get_security_support_template(&self) -> Result<Tlv> {
|
pub fn get_security_support_template(&self) -> Result<Tlv> {
|
||||||
let sst = commands::get_security_support_template();
|
self.card_app.get_security_support_template()
|
||||||
|
|
||||||
let ext = self.get_extended_length_information()?.is_some();
|
|
||||||
let ext = if ext { Le::Long } else { Le::Short };
|
|
||||||
|
|
||||||
let resp = apdu::send_command(&self.card, sst, ext, Some(self))?;
|
|
||||||
resp.check_ok()?;
|
|
||||||
|
|
||||||
log::trace!(
|
|
||||||
" final response: {:x?}, data len {}",
|
|
||||||
resp,
|
|
||||||
resp.data()?.len()
|
|
||||||
);
|
|
||||||
|
|
||||||
Tlv::try_from(resp.data()?)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DO "Algorithm Information" (0xFA)
|
// DO "Algorithm Information" (0xFA)
|
||||||
|
@ -509,63 +439,14 @@ impl CardBase {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let resp = apdu::send_command(
|
self.card_app.list_supported_algo()
|
||||||
&self.card,
|
|
||||||
commands::get_algo_list(),
|
|
||||||
Le::Short,
|
|
||||||
Some(self),
|
|
||||||
)?;
|
|
||||||
resp.check_ok()?;
|
|
||||||
|
|
||||||
let ai = AlgoInfo::try_from(resp.data()?)?;
|
|
||||||
Ok(Some(ai))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------
|
// ----------
|
||||||
|
|
||||||
/// Delete all state on this OpenPGP card
|
/// Delete all state on this OpenPGP card
|
||||||
pub fn factory_reset(&self) -> Result<()> {
|
pub fn factory_reset(&self) -> Result<()> {
|
||||||
// send 4 bad requests to verify pw1
|
self.card_app.factory_reset()
|
||||||
// [apdu 00 20 00 81 08 40 40 40 40 40 40 40 40]
|
|
||||||
for _ in 0..4 {
|
|
||||||
let verify = commands::verify_pw1_81([0x40; 8].to_vec());
|
|
||||||
let resp =
|
|
||||||
apdu::send_command(&self.card, verify, Le::None, Some(self))?;
|
|
||||||
if !(resp.status() == [0x69, 0x82]
|
|
||||||
|| resp.status() == [0x69, 0x83])
|
|
||||||
{
|
|
||||||
return Err(anyhow!("Unexpected status for reset, at pw1."));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// send 4 bad requests to verify pw3
|
|
||||||
// [apdu 00 20 00 83 08 40 40 40 40 40 40 40 40]
|
|
||||||
for _ in 0..4 {
|
|
||||||
let verify = commands::verify_pw3([0x40; 8].to_vec());
|
|
||||||
let resp =
|
|
||||||
apdu::send_command(&self.card, verify, Le::None, Some(self))?;
|
|
||||||
|
|
||||||
if !(resp.status() == [0x69, 0x82]
|
|
||||||
|| resp.status() == [0x69, 0x83])
|
|
||||||
{
|
|
||||||
return Err(anyhow!("Unexpected status for reset, at pw3."));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// terminate_df [apdu 00 e6 00 00]
|
|
||||||
let term = commands::terminate_df();
|
|
||||||
let resp = apdu::send_command(&self.card, term, Le::None, Some(self))?;
|
|
||||||
resp.check_ok()?;
|
|
||||||
|
|
||||||
// activate_file [apdu 00 44 00 00]
|
|
||||||
let act = commands::activate_file();
|
|
||||||
let resp = apdu::send_command(&self.card, act, Le::None, Some(self))?;
|
|
||||||
resp.check_ok()?;
|
|
||||||
|
|
||||||
// FIXME: does the connection need to be re-opened on some cards,
|
|
||||||
// after reset?!
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn verify_pw1_for_signing(
|
pub fn verify_pw1_for_signing(
|
||||||
|
@ -574,9 +455,7 @@ impl CardBase {
|
||||||
) -> Result<CardSign, CardBase> {
|
) -> Result<CardSign, CardBase> {
|
||||||
assert!(pin.len() >= 6); // FIXME: Err
|
assert!(pin.len() >= 6); // FIXME: Err
|
||||||
|
|
||||||
let verify = commands::verify_pw1_81(pin.as_bytes().to_vec());
|
let res = self.card_app.verify_pw1_for_signing(pin);
|
||||||
let res =
|
|
||||||
apdu::send_command(&self.card, verify, Le::None, Some(&self));
|
|
||||||
|
|
||||||
if let Ok(resp) = res {
|
if let Ok(resp) = res {
|
||||||
if resp.is_ok() {
|
if resp.is_ok() {
|
||||||
|
@ -588,16 +467,13 @@ impl CardBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_pw1(&self) -> Result<Response, OpenpgpCardError> {
|
pub fn check_pw1(&self) -> Result<Response, OpenpgpCardError> {
|
||||||
let verify = commands::verify_pw1_82(vec![]);
|
self.card_app.check_pw1()
|
||||||
apdu::send_command(&self.card, verify, Le::None, Some(&self))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn verify_pw1(self, pin: &str) -> Result<CardUser, CardBase> {
|
pub fn verify_pw1(self, pin: &str) -> Result<CardUser, CardBase> {
|
||||||
assert!(pin.len() >= 6); // FIXME: Err
|
assert!(pin.len() >= 6); // FIXME: Err
|
||||||
|
|
||||||
let verify = commands::verify_pw1_82(pin.as_bytes().to_vec());
|
let res = self.card_app.verify_pw1(pin);
|
||||||
let res =
|
|
||||||
apdu::send_command(&self.card, verify, Le::None, Some(&self));
|
|
||||||
|
|
||||||
if let Ok(resp) = res {
|
if let Ok(resp) = res {
|
||||||
if resp.is_ok() {
|
if resp.is_ok() {
|
||||||
|
@ -609,16 +485,13 @@ impl CardBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_pw3(&self) -> Result<Response, OpenpgpCardError> {
|
pub fn check_pw3(&self) -> Result<Response, OpenpgpCardError> {
|
||||||
let verify = commands::verify_pw3(vec![]);
|
self.card_app.check_pw3()
|
||||||
apdu::send_command(&self.card, verify, Le::None, Some(&self))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn verify_pw3(self, pin: &str) -> Result<CardAdmin, CardBase> {
|
pub fn verify_pw3(self, pin: &str) -> Result<CardAdmin, CardBase> {
|
||||||
assert!(pin.len() >= 8); // FIXME: Err
|
assert!(pin.len() >= 8); // FIXME: Err
|
||||||
|
|
||||||
let verify = commands::verify_pw3(pin.as_bytes().to_vec());
|
let res = self.card_app.verify_pw3(pin);
|
||||||
let res =
|
|
||||||
apdu::send_command(&self.card, verify, Le::None, Some(&self));
|
|
||||||
|
|
||||||
if let Ok(resp) = res {
|
if let Ok(resp) = res {
|
||||||
if resp.is_ok() {
|
if resp.is_ok() {
|
||||||
|
@ -648,27 +521,7 @@ impl Deref for CardUser {
|
||||||
impl CardUser {
|
impl CardUser {
|
||||||
/// Decrypt the ciphertext in `dm`, on the card.
|
/// Decrypt the ciphertext in `dm`, on the card.
|
||||||
pub fn decrypt(&self, dm: DecryptMe) -> Result<Vec<u8>, OpenpgpCardError> {
|
pub fn decrypt(&self, dm: DecryptMe) -> Result<Vec<u8>, OpenpgpCardError> {
|
||||||
match dm {
|
self.card_app.decrypt(dm)
|
||||||
DecryptMe::RSA(message) => {
|
|
||||||
let mut data = vec![0x0];
|
|
||||||
data.extend_from_slice(message);
|
|
||||||
|
|
||||||
// Call the card to decrypt `data`
|
|
||||||
self.pso_decipher(data)
|
|
||||||
}
|
|
||||||
DecryptMe::ECDH(eph) => {
|
|
||||||
// External Public Key
|
|
||||||
let epk = Tlv(Tag(vec![0x86]), TlvEntry::S(eph.to_vec()));
|
|
||||||
|
|
||||||
// Public Key DO
|
|
||||||
let pkdo = Tlv(Tag(vec![0x7f, 0x49]), TlvEntry::C(vec![epk]));
|
|
||||||
|
|
||||||
// Cipher DO
|
|
||||||
let cdo = Tlv(Tag(vec![0xa6]), TlvEntry::C(vec![pkdo]));
|
|
||||||
|
|
||||||
self.pso_decipher(cdo.serialize())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run decryption operation on the smartcard
|
/// Run decryption operation on the smartcard
|
||||||
|
@ -677,13 +530,7 @@ impl CardUser {
|
||||||
&self,
|
&self,
|
||||||
data: Vec<u8>,
|
data: Vec<u8>,
|
||||||
) -> Result<Vec<u8>, OpenpgpCardError> {
|
) -> Result<Vec<u8>, OpenpgpCardError> {
|
||||||
// The OpenPGP card is already connected and PW1 82 has been verified
|
self.card_app.pso_decipher(data)
|
||||||
let dec_cmd = commands::decryption(data);
|
|
||||||
let resp =
|
|
||||||
apdu::send_command(&self.card, dec_cmd, Le::Short, Some(self))?;
|
|
||||||
resp.check_ok()?;
|
|
||||||
|
|
||||||
Ok(resp.data().map(|d| d.to_vec())?)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -710,33 +557,7 @@ impl CardSign {
|
||||||
&self,
|
&self,
|
||||||
hash: Hash,
|
hash: Hash,
|
||||||
) -> Result<Vec<u8>, OpenpgpCardError> {
|
) -> Result<Vec<u8>, OpenpgpCardError> {
|
||||||
match hash {
|
self.card_app.signature_for_hash(hash)
|
||||||
Hash::SHA256(_) | Hash::SHA384(_) | Hash::SHA512(_) => {
|
|
||||||
let tlv = Tlv(
|
|
||||||
Tag(vec![0x30]),
|
|
||||||
TlvEntry::C(vec![
|
|
||||||
Tlv(
|
|
||||||
Tag(vec![0x30]),
|
|
||||||
TlvEntry::C(vec![
|
|
||||||
Tlv(
|
|
||||||
Tag(vec![0x06]),
|
|
||||||
// unwrapping is ok, for SHA*
|
|
||||||
TlvEntry::S(hash.oid().unwrap().to_vec()),
|
|
||||||
),
|
|
||||||
Tlv(Tag(vec![0x05]), TlvEntry::S(vec![])),
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
Tlv(
|
|
||||||
Tag(vec![0x04]),
|
|
||||||
TlvEntry::S(hash.digest().to_vec()),
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(self.compute_digital_signature(tlv.serialize())?)
|
|
||||||
}
|
|
||||||
Hash::EdDSA(d) => Ok(self.compute_digital_signature(d.to_vec())?),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run signing operation on the smartcard
|
/// Run signing operation on the smartcard
|
||||||
|
@ -745,12 +566,7 @@ impl CardSign {
|
||||||
&self,
|
&self,
|
||||||
data: Vec<u8>,
|
data: Vec<u8>,
|
||||||
) -> Result<Vec<u8>, OpenpgpCardError> {
|
) -> Result<Vec<u8>, OpenpgpCardError> {
|
||||||
let dec_cmd = commands::signature(data);
|
self.card_app.compute_digital_signature(data)
|
||||||
|
|
||||||
let resp =
|
|
||||||
apdu::send_command(&self.card, dec_cmd, Le::Short, Some(self))?;
|
|
||||||
|
|
||||||
Ok(resp.data().map(|d| d.to_vec())?)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -769,10 +585,6 @@ impl Deref for CardAdmin {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CardAdmin {
|
impl CardAdmin {
|
||||||
pub(crate) fn card(&self) -> &Card {
|
|
||||||
&self.card
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_name(&self, name: &str) -> Result<Response, OpenpgpCardError> {
|
pub fn set_name(&self, name: &str) -> Result<Response, OpenpgpCardError> {
|
||||||
if name.len() >= 40 {
|
if name.len() >= 40 {
|
||||||
return Err(anyhow!("name too long").into());
|
return Err(anyhow!("name too long").into());
|
||||||
|
@ -783,8 +595,7 @@ impl CardAdmin {
|
||||||
return Err(anyhow!("Invalid char in name").into());
|
return Err(anyhow!("Invalid char in name").into());
|
||||||
};
|
};
|
||||||
|
|
||||||
let put_name = commands::put_name(name.as_bytes().to_vec());
|
self.card_app.set_name(name)
|
||||||
apdu::send_command(self.card(), put_name, Le::None, Some(self))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_lang(&self, lang: &str) -> Result<Response, OpenpgpCardError> {
|
pub fn set_lang(&self, lang: &str) -> Result<Response, OpenpgpCardError> {
|
||||||
|
@ -792,13 +603,11 @@ impl CardAdmin {
|
||||||
return Err(anyhow!("lang too long").into());
|
return Err(anyhow!("lang too long").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let put_lang = commands::put_lang(lang.as_bytes().to_vec());
|
self.card_app.set_lang(lang)
|
||||||
apdu::send_command(self.card(), put_lang, Le::None, Some(self))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_sex(&self, sex: Sex) -> Result<Response, OpenpgpCardError> {
|
pub fn set_sex(&self, sex: Sex) -> Result<Response, OpenpgpCardError> {
|
||||||
let put_sex = commands::put_sex(sex.as_u8());
|
self.card_app.set_sex(sex)
|
||||||
apdu::send_command(self.card(), put_sex, Le::None, Some(self))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_url(&self, url: &str) -> Result<Response, OpenpgpCardError> {
|
pub fn set_url(&self, url: &str) -> Result<Response, OpenpgpCardError> {
|
||||||
|
@ -810,8 +619,7 @@ impl CardAdmin {
|
||||||
let ec = self.get_extended_capabilities()?;
|
let ec = self.get_extended_capabilities()?;
|
||||||
|
|
||||||
if url.len() < ec.max_len_special_do as usize {
|
if url.len() < ec.max_len_special_do as usize {
|
||||||
let put_url = commands::put_url(url.as_bytes().to_vec());
|
self.card_app.set_url(url)
|
||||||
apdu::send_command(self.card(), put_url, Le::None, Some(self))
|
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!("URL too long").into())
|
Err(anyhow!("URL too long").into())
|
||||||
}
|
}
|
||||||
|
@ -822,7 +630,9 @@ impl CardAdmin {
|
||||||
key: Box<dyn CardUploadableKey>,
|
key: Box<dyn CardUploadableKey>,
|
||||||
key_type: KeyType,
|
key_type: KeyType,
|
||||||
) -> Result<(), OpenpgpCardError> {
|
) -> Result<(), OpenpgpCardError> {
|
||||||
key_upload::upload_key(self, key, key_type)
|
let algo_list = self.list_supported_algo()?;
|
||||||
|
|
||||||
|
key_upload::upload_key(&self.card_app, key, key_type, algo_list)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue