Move OpenPGP card functionality into OpenPgp/OpenPgpTransaction.
This separates backend access (implemented in CardBackend and CardTransaction) from OpenPGP card operations. Fixes #7
This commit is contained in:
parent
16b1b5136c
commit
96167f6530
17 changed files with 1098 additions and 995 deletions
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
use openpgp_card::CardBackend;
|
use openpgp_card::OpenPgp;
|
||||||
use openpgp_card_pcsc::PcscBackend;
|
use openpgp_card_pcsc::PcscBackend;
|
||||||
use openpgp_card_sequoia::card::Open;
|
use openpgp_card_sequoia::card::Open;
|
||||||
|
|
||||||
|
@ -11,9 +11,8 @@ fn main() -> Result<()> {
|
||||||
println!("The following OpenPGP cards are connected to your system:");
|
println!("The following OpenPGP cards are connected to your system:");
|
||||||
|
|
||||||
for mut card in PcscBackend::cards(None)? {
|
for mut card in PcscBackend::cards(None)? {
|
||||||
let mut txc = card.transaction()?;
|
let mut pgp = OpenPgp::new(&mut card);
|
||||||
|
let open = Open::new(pgp.transaction()?)?;
|
||||||
let open = Open::new(&mut *txc)?;
|
|
||||||
println!(" {}", open.application_identifier()?.ident());
|
println!(" {}", open.application_identifier()?.ident());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// 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 anyhow::{anyhow, Result};
|
use anyhow::Result;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::string::FromUtf8Error;
|
use std::string::FromUtf8Error;
|
||||||
|
@ -12,10 +12,9 @@ use sequoia_openpgp::policy::StandardPolicy;
|
||||||
use sequoia_openpgp::serialize::SerializeInto;
|
use sequoia_openpgp::serialize::SerializeInto;
|
||||||
use sequoia_openpgp::Cert;
|
use sequoia_openpgp::Cert;
|
||||||
|
|
||||||
use openpgp_card;
|
|
||||||
use openpgp_card::algorithm::AlgoSimple;
|
use openpgp_card::algorithm::AlgoSimple;
|
||||||
use openpgp_card::card_do::{KeyGenerationTime, Sex};
|
use openpgp_card::card_do::{KeyGenerationTime, Sex};
|
||||||
use openpgp_card::{CardTransaction, Error, KeyType, StatusBytes};
|
use openpgp_card::{CardBackend, Error, KeyType, OpenPgp, OpenPgpTransaction, StatusBytes};
|
||||||
use openpgp_card_sequoia::card::Open;
|
use openpgp_card_sequoia::card::Open;
|
||||||
use openpgp_card_sequoia::util::{make_cert, public_key_material_to_key, public_to_fingerprint};
|
use openpgp_card_sequoia::util::{make_cert, public_key_material_to_key, public_to_fingerprint};
|
||||||
|
|
||||||
|
@ -50,10 +49,10 @@ pub enum TestError {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run after each "upload keys", if key *was* uploaded (?)
|
/// Run after each "upload keys", if key *was* uploaded (?)
|
||||||
pub fn test_decrypt(
|
pub fn test_decrypt(card: &mut dyn CardBackend, param: &[&str]) -> Result<TestOutput, TestError> {
|
||||||
card_tx: &mut (dyn CardTransaction + Send + Sync),
|
let mut pgp = OpenPgp::new(card);
|
||||||
param: &[&str],
|
let mut pgpt = pgp.transaction()?;
|
||||||
) -> Result<TestOutput, TestError> {
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
param.len(),
|
param.len(),
|
||||||
2,
|
2,
|
||||||
|
@ -63,11 +62,11 @@ pub fn test_decrypt(
|
||||||
let cert = Cert::from_str(param[0])?;
|
let cert = Cert::from_str(param[0])?;
|
||||||
let msg = param[1].to_string();
|
let msg = param[1].to_string();
|
||||||
|
|
||||||
card_tx.verify_pw1("123456")?;
|
pgpt.verify_pw1("123456")?;
|
||||||
|
|
||||||
let p = StandardPolicy::new();
|
let p = StandardPolicy::new();
|
||||||
|
|
||||||
let res = openpgp_card_sequoia::util::decrypt(card_tx, &cert, msg.into_bytes(), &p)?;
|
let res = openpgp_card_sequoia::util::decrypt(&mut pgpt, &cert, msg.into_bytes(), &p)?;
|
||||||
let plain = String::from_utf8_lossy(&res);
|
let plain = String::from_utf8_lossy(&res);
|
||||||
|
|
||||||
assert_eq!(plain, "Hello world!\n");
|
assert_eq!(plain, "Hello world!\n");
|
||||||
|
@ -76,18 +75,18 @@ pub fn test_decrypt(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run after each "upload keys", if key *was* uploaded (?)
|
/// Run after each "upload keys", if key *was* uploaded (?)
|
||||||
pub fn test_sign(
|
pub fn test_sign(card: &mut dyn CardBackend, param: &[&str]) -> Result<TestOutput, TestError> {
|
||||||
card_tx: &mut (dyn CardTransaction + Send + Sync),
|
let mut pgp = OpenPgp::new(card);
|
||||||
param: &[&str],
|
let mut pgpt = pgp.transaction()?;
|
||||||
) -> Result<TestOutput, TestError> {
|
|
||||||
assert_eq!(param.len(), 1, "test_sign needs a filename for 'cert'");
|
assert_eq!(param.len(), 1, "test_sign needs a filename for 'cert'");
|
||||||
|
|
||||||
card_tx.verify_pw1_for_signing("123456")?;
|
pgpt.verify_pw1_for_signing("123456")?;
|
||||||
|
|
||||||
let cert = Cert::from_str(param[0])?;
|
let cert = Cert::from_str(param[0])?;
|
||||||
|
|
||||||
let msg = "Hello world, I am signed.";
|
let msg = "Hello world, I am signed.";
|
||||||
let sig = openpgp_card_sequoia::util::sign(card_tx, &cert, &mut msg.as_bytes())?;
|
let sig = openpgp_card_sequoia::util::sign(&mut pgpt, &cert, &mut msg.as_bytes())?;
|
||||||
|
|
||||||
// validate sig
|
// validate sig
|
||||||
assert!(util::verify_sig(&cert, msg.as_bytes(), sig.as_bytes())?);
|
assert!(util::verify_sig(&cert, msg.as_bytes(), sig.as_bytes())?);
|
||||||
|
@ -96,10 +95,10 @@ pub fn test_sign(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_key_upload_metadata(
|
fn check_key_upload_metadata(
|
||||||
card_tx: &mut (dyn CardTransaction + Send + Sync),
|
pgpt: &mut OpenPgpTransaction,
|
||||||
meta: &[(String, KeyGenerationTime)],
|
meta: &[(String, KeyGenerationTime)],
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let ard = card_tx.application_related_data()?;
|
let ard = pgpt.application_related_data()?;
|
||||||
|
|
||||||
// check fingerprints
|
// check fingerprints
|
||||||
let card_fp = ard.fingerprints()?;
|
let card_fp = ard.fingerprints()?;
|
||||||
|
@ -140,10 +139,13 @@ fn check_key_upload_algo_attrs() -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn test_print_caps(
|
pub fn test_print_caps(
|
||||||
card_tx: &mut (dyn CardTransaction + Send + Sync),
|
card: &mut dyn CardBackend,
|
||||||
_param: &[&str],
|
_param: &[&str],
|
||||||
) -> Result<TestOutput, TestError> {
|
) -> Result<TestOutput, TestError> {
|
||||||
let ard = card_tx.application_related_data()?;
|
let mut pgp = OpenPgp::new(card);
|
||||||
|
let mut pgpt = pgp.transaction()?;
|
||||||
|
|
||||||
|
let ard = pgpt.application_related_data()?;
|
||||||
|
|
||||||
let aid = ard.application_id()?;
|
let aid = ard.application_id()?;
|
||||||
println!("aid: {:#x?}", aid);
|
println!("aid: {:#x?}", aid);
|
||||||
|
@ -161,17 +163,20 @@ pub fn test_print_caps(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn test_print_algo_info(
|
pub fn test_print_algo_info(
|
||||||
card_tx: &mut (dyn CardTransaction + Send + Sync),
|
card: &mut dyn CardBackend,
|
||||||
_param: &[&str],
|
_param: &[&str],
|
||||||
) -> Result<TestOutput, TestError> {
|
) -> Result<TestOutput, TestError> {
|
||||||
let ard = card_tx.application_related_data()?;
|
let mut pgp = OpenPgp::new(card);
|
||||||
|
let mut pgpt = pgp.transaction()?;
|
||||||
|
|
||||||
|
let ard = pgpt.application_related_data()?;
|
||||||
|
|
||||||
let dec = ard.algorithm_attributes(KeyType::Decryption)?;
|
let dec = ard.algorithm_attributes(KeyType::Decryption)?;
|
||||||
println!("Current algorithm for the decrypt slot: {}", dec);
|
println!("Current algorithm for the decrypt slot: {}", dec);
|
||||||
|
|
||||||
println!();
|
println!();
|
||||||
|
|
||||||
let algo = card_tx.algorithm_information();
|
let algo = pgpt.algorithm_information();
|
||||||
if let Ok(Some(algo)) = algo {
|
if let Ok(Some(algo)) = algo {
|
||||||
println!("Card algorithm list:\n{}", algo);
|
println!("Card algorithm list:\n{}", algo);
|
||||||
}
|
}
|
||||||
|
@ -180,25 +185,28 @@ pub fn test_print_algo_info(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn test_upload_keys(
|
pub fn test_upload_keys(
|
||||||
card_tx: &mut (dyn CardTransaction + Send + Sync),
|
card: &mut dyn CardBackend,
|
||||||
param: &[&str],
|
param: &[&str],
|
||||||
) -> Result<TestOutput, TestError> {
|
) -> Result<TestOutput, TestError> {
|
||||||
|
let mut pgp = OpenPgp::new(card);
|
||||||
|
let mut pgpt = pgp.transaction()?;
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
param.len(),
|
param.len(),
|
||||||
1,
|
1,
|
||||||
"test_upload_keys needs a filename for 'cert'"
|
"test_upload_keys needs a filename for 'cert'"
|
||||||
);
|
);
|
||||||
|
|
||||||
card_tx.verify_pw3("12345678")?;
|
pgpt.verify_pw3("12345678")?;
|
||||||
|
|
||||||
let cert = Cert::from_file(param[0])?;
|
let cert = Cert::from_file(param[0])?;
|
||||||
|
|
||||||
let p = StandardPolicy::new();
|
let p = StandardPolicy::new();
|
||||||
|
|
||||||
let meta = util::upload_subkeys(card_tx, &cert, &p)
|
let meta = util::upload_subkeys(&mut pgpt, &cert, &p)
|
||||||
.map_err(|e| TestError::KeyUploadError(param[0].to_string(), e))?;
|
.map_err(|e| TestError::KeyUploadError(param[0].to_string(), e))?;
|
||||||
|
|
||||||
check_key_upload_metadata(card_tx, &meta)?;
|
check_key_upload_metadata(&mut pgpt, &meta)?;
|
||||||
|
|
||||||
// FIXME: implement
|
// FIXME: implement
|
||||||
check_key_upload_algo_attrs()?;
|
check_key_upload_algo_attrs()?;
|
||||||
|
@ -207,11 +215,11 @@ pub fn test_upload_keys(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate keys for each of the three KeyTypes
|
/// Generate keys for each of the three KeyTypes
|
||||||
pub fn test_keygen(
|
pub fn test_keygen(card: &mut dyn CardBackend, param: &[&str]) -> Result<TestOutput, TestError> {
|
||||||
card_tx: &mut (dyn CardTransaction + Send + Sync),
|
let mut pgp = OpenPgp::new(card);
|
||||||
param: &[&str],
|
let mut pgpt = pgp.transaction()?;
|
||||||
) -> Result<TestOutput, TestError> {
|
|
||||||
card_tx.verify_pw3("12345678")?;
|
pgpt.verify_pw3("12345678")?;
|
||||||
|
|
||||||
// Generate all three subkeys on card
|
// Generate all three subkeys on card
|
||||||
let algo = param[0];
|
let algo = param[0];
|
||||||
|
@ -219,20 +227,20 @@ pub fn test_keygen(
|
||||||
let alg = AlgoSimple::try_from(algo)?;
|
let alg = AlgoSimple::try_from(algo)?;
|
||||||
|
|
||||||
println!(" Generate subkey for Signing");
|
println!(" Generate subkey for Signing");
|
||||||
let (pkm, ts) = card_tx.generate_key_simple(public_to_fingerprint, KeyType::Signing, alg)?;
|
let (pkm, ts) = pgpt.generate_key_simple(public_to_fingerprint, KeyType::Signing, alg)?;
|
||||||
let key_sig = public_key_material_to_key(&pkm, KeyType::Signing, ts)?;
|
let key_sig = public_key_material_to_key(&pkm, KeyType::Signing, ts)?;
|
||||||
|
|
||||||
println!(" Generate subkey for Decryption");
|
println!(" Generate subkey for Decryption");
|
||||||
let (pkm, ts) = card_tx.generate_key_simple(public_to_fingerprint, KeyType::Decryption, alg)?;
|
let (pkm, ts) = pgpt.generate_key_simple(public_to_fingerprint, KeyType::Decryption, alg)?;
|
||||||
let key_dec = public_key_material_to_key(&pkm, KeyType::Decryption, ts)?;
|
let key_dec = public_key_material_to_key(&pkm, KeyType::Decryption, ts)?;
|
||||||
|
|
||||||
println!(" Generate subkey for Authentication");
|
println!(" Generate subkey for Authentication");
|
||||||
let (pkm, ts) =
|
let (pkm, ts) =
|
||||||
card_tx.generate_key_simple(public_to_fingerprint, KeyType::Authentication, alg)?;
|
pgpt.generate_key_simple(public_to_fingerprint, KeyType::Authentication, alg)?;
|
||||||
let key_aut = public_key_material_to_key(&pkm, KeyType::Authentication, ts)?;
|
let key_aut = public_key_material_to_key(&pkm, KeyType::Authentication, ts)?;
|
||||||
|
|
||||||
// Generate a Cert for this set of generated keys
|
// Generate a Cert for this set of generated keys
|
||||||
let mut open = Open::new(card_tx)?;
|
let mut open = Open::new(pgpt)?;
|
||||||
let cert = make_cert(
|
let cert = make_cert(
|
||||||
&mut open,
|
&mut open,
|
||||||
key_sig,
|
key_sig,
|
||||||
|
@ -249,16 +257,16 @@ pub fn test_keygen(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct public key based on data from the card
|
/// Construct public key based on data from the card
|
||||||
pub fn test_get_pub(
|
pub fn test_get_pub(card: &mut dyn CardBackend, _param: &[&str]) -> Result<TestOutput, TestError> {
|
||||||
card_tx: &mut (dyn CardTransaction + Send + Sync),
|
let mut pgp = OpenPgp::new(card);
|
||||||
_param: &[&str],
|
let mut pgpt = pgp.transaction()?;
|
||||||
) -> Result<TestOutput, TestError> {
|
|
||||||
let ard = card_tx.application_related_data()?;
|
let ard = pgpt.application_related_data()?;
|
||||||
let key_gen = ard.key_generation_times()?;
|
let key_gen = ard.key_generation_times()?;
|
||||||
|
|
||||||
// --
|
// --
|
||||||
|
|
||||||
let sig = card_tx.public_key(KeyType::Signing)?;
|
let sig = pgpt.public_key(KeyType::Signing)?;
|
||||||
let ts = key_gen.signature().unwrap().get().into();
|
let ts = key_gen.signature().unwrap().get().into();
|
||||||
let key = public_key_material_to_key(&sig, KeyType::Signing, ts)?;
|
let key = public_key_material_to_key(&sig, KeyType::Signing, ts)?;
|
||||||
|
|
||||||
|
@ -266,7 +274,7 @@ pub fn test_get_pub(
|
||||||
|
|
||||||
// --
|
// --
|
||||||
|
|
||||||
let dec = card_tx.public_key(KeyType::Decryption)?;
|
let dec = pgpt.public_key(KeyType::Decryption)?;
|
||||||
let ts = key_gen.decryption().unwrap().get().into();
|
let ts = key_gen.decryption().unwrap().get().into();
|
||||||
let key = public_key_material_to_key(&dec, KeyType::Decryption, ts)?;
|
let key = public_key_material_to_key(&dec, KeyType::Decryption, ts)?;
|
||||||
|
|
||||||
|
@ -274,7 +282,7 @@ pub fn test_get_pub(
|
||||||
|
|
||||||
// --
|
// --
|
||||||
|
|
||||||
let auth = card_tx.public_key(KeyType::Authentication)?;
|
let auth = pgpt.public_key(KeyType::Authentication)?;
|
||||||
let ts = key_gen.authentication().unwrap().get().into();
|
let ts = key_gen.authentication().unwrap().get().into();
|
||||||
let key = public_key_material_to_key(&auth, KeyType::Authentication, ts)?;
|
let key = public_key_material_to_key(&auth, KeyType::Authentication, ts)?;
|
||||||
|
|
||||||
|
@ -288,11 +296,11 @@ pub fn test_get_pub(
|
||||||
Ok(vec![])
|
Ok(vec![])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn test_reset(
|
pub fn test_reset(card: &mut dyn CardBackend, _param: &[&str]) -> Result<TestOutput, TestError> {
|
||||||
card_tx: &mut (dyn CardTransaction + Send + Sync),
|
let mut pgp = OpenPgp::new(card);
|
||||||
_param: &[&str],
|
let mut pgpt = pgp.transaction()?;
|
||||||
) -> Result<TestOutput, TestError> {
|
|
||||||
let _res = card_tx.factory_reset()?;
|
let _res = pgpt.factory_reset()?;
|
||||||
Ok(vec![])
|
Ok(vec![])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -302,25 +310,28 @@ pub fn test_reset(
|
||||||
/// Returns an empty TestOutput, throws errors for unexpected Status codes
|
/// Returns an empty TestOutput, throws errors for unexpected Status codes
|
||||||
/// and for unequal field values.
|
/// and for unequal field values.
|
||||||
pub fn test_set_user_data(
|
pub fn test_set_user_data(
|
||||||
card_tx: &mut (dyn CardTransaction + Send + Sync),
|
card: &mut dyn CardBackend,
|
||||||
_param: &[&str],
|
_param: &[&str],
|
||||||
) -> Result<TestOutput, TestError> {
|
) -> Result<TestOutput, TestError> {
|
||||||
card_tx.verify_pw3("12345678")?;
|
let mut pgp = OpenPgp::new(card);
|
||||||
|
let mut pgpt = pgp.transaction()?;
|
||||||
|
|
||||||
|
pgpt.verify_pw3("12345678")?;
|
||||||
|
|
||||||
// name
|
// name
|
||||||
card_tx.set_name(b"Bar<<Foo")?;
|
pgpt.set_name(b"Bar<<Foo")?;
|
||||||
|
|
||||||
// lang
|
// lang
|
||||||
card_tx.set_lang(&[['d', 'e'].into(), ['e', 'n'].into()])?;
|
pgpt.set_lang(&[['d', 'e'].into(), ['e', 'n'].into()])?;
|
||||||
|
|
||||||
// sex
|
// sex
|
||||||
card_tx.set_sex(Sex::Female)?;
|
pgpt.set_sex(Sex::Female)?;
|
||||||
|
|
||||||
// url
|
// url
|
||||||
card_tx.set_url(b"https://duckduckgo.com/")?;
|
pgpt.set_url(b"https://duckduckgo.com/")?;
|
||||||
|
|
||||||
// read all the fields back again, expect equal data
|
// read all the fields back again, expect equal data
|
||||||
let ch = card_tx.cardholder_related_data()?;
|
let ch = pgpt.cardholder_related_data()?;
|
||||||
|
|
||||||
assert_eq!(ch.name(), Some("Bar<<Foo".as_bytes()));
|
assert_eq!(ch.name(), Some("Bar<<Foo".as_bytes()));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -329,47 +340,50 @@ pub fn test_set_user_data(
|
||||||
);
|
);
|
||||||
assert_eq!(ch.sex(), Some(Sex::Female));
|
assert_eq!(ch.sex(), Some(Sex::Female));
|
||||||
|
|
||||||
let url = card_tx.url()?;
|
let url = pgpt.url()?;
|
||||||
assert_eq!(&url, b"https://duckduckgo.com/");
|
assert_eq!(&url, b"https://duckduckgo.com/");
|
||||||
|
|
||||||
Ok(vec![])
|
Ok(vec![])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn test_private_data(
|
pub fn test_private_data(
|
||||||
card_tx: &mut (dyn CardTransaction + Send + Sync),
|
card: &mut dyn CardBackend,
|
||||||
_param: &[&str],
|
_param: &[&str],
|
||||||
) -> Result<TestOutput, TestError> {
|
) -> Result<TestOutput, TestError> {
|
||||||
|
let mut pgp = OpenPgp::new(card);
|
||||||
|
let mut pgpt = pgp.transaction()?;
|
||||||
|
|
||||||
let out = vec![];
|
let out = vec![];
|
||||||
|
|
||||||
println!();
|
println!();
|
||||||
|
|
||||||
let d = card_tx.private_use_do(1)?;
|
let d = pgpt.private_use_do(1)?;
|
||||||
println!("data 1 {:?}", d);
|
println!("data 1 {:?}", d);
|
||||||
|
|
||||||
card_tx.verify_pw1("123456")?;
|
pgpt.verify_pw1("123456")?;
|
||||||
|
|
||||||
card_tx.set_private_use_do(1, "Foo bar1!".as_bytes().to_vec())?;
|
pgpt.set_private_use_do(1, "Foo bar1!".as_bytes().to_vec())?;
|
||||||
card_tx.set_private_use_do(3, "Foo bar3!".as_bytes().to_vec())?;
|
pgpt.set_private_use_do(3, "Foo bar3!".as_bytes().to_vec())?;
|
||||||
|
|
||||||
card_tx.verify_pw3("12345678")?;
|
pgpt.verify_pw3("12345678")?;
|
||||||
|
|
||||||
card_tx.set_private_use_do(2, "Foo bar2!".as_bytes().to_vec())?;
|
pgpt.set_private_use_do(2, "Foo bar2!".as_bytes().to_vec())?;
|
||||||
card_tx.set_private_use_do(4, "Foo bar4!".as_bytes().to_vec())?;
|
pgpt.set_private_use_do(4, "Foo bar4!".as_bytes().to_vec())?;
|
||||||
|
|
||||||
let d = card_tx.private_use_do(1)?;
|
let d = pgpt.private_use_do(1)?;
|
||||||
println!("data 1 {:?}", d);
|
println!("data 1 {:?}", d);
|
||||||
let d = card_tx.private_use_do(2)?;
|
let d = pgpt.private_use_do(2)?;
|
||||||
println!("data 2 {:?}", d);
|
println!("data 2 {:?}", d);
|
||||||
let d = card_tx.private_use_do(3)?;
|
let d = pgpt.private_use_do(3)?;
|
||||||
println!("data 3 {:?}", d);
|
println!("data 3 {:?}", d);
|
||||||
let d = card_tx.private_use_do(4)?;
|
let d = pgpt.private_use_do(4)?;
|
||||||
println!("data 4 {:?}", d);
|
println!("data 4 {:?}", d);
|
||||||
|
|
||||||
Ok(out)
|
Ok(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
// pub fn test_cardholder_cert(
|
// pub fn test_cardholder_cert(
|
||||||
// card_tx: &mut (dyn CardTransaction + Send + Sync),
|
// card_tx: &mut CardApp,
|
||||||
// _param: &[&str],
|
// _param: &[&str],
|
||||||
// ) -> Result<TestOutput, TestError> {
|
// ) -> Result<TestOutput, TestError> {
|
||||||
// let mut out = vec![];
|
// let mut out = vec![];
|
||||||
|
@ -429,24 +443,27 @@ pub fn test_private_data(
|
||||||
// }
|
// }
|
||||||
|
|
||||||
pub fn test_pw_status(
|
pub fn test_pw_status(
|
||||||
card_tx: &mut (dyn CardTransaction + Send + Sync),
|
card: &mut dyn CardBackend,
|
||||||
_param: &[&str],
|
_param: &[&str],
|
||||||
) -> Result<TestOutput, TestError> {
|
) -> Result<TestOutput, TestError> {
|
||||||
|
let mut pgp = OpenPgp::new(card);
|
||||||
|
let mut pgpt = pgp.transaction()?;
|
||||||
|
|
||||||
let out = vec![];
|
let out = vec![];
|
||||||
|
|
||||||
let ard = card_tx.application_related_data()?;
|
let ard = pgpt.application_related_data()?;
|
||||||
let mut pws = ard.pw_status_bytes()?;
|
let mut pws = ard.pw_status_bytes()?;
|
||||||
|
|
||||||
println!("pws {:?}", pws);
|
println!("pws {:?}", pws);
|
||||||
|
|
||||||
card_tx.verify_pw3("12345678")?;
|
pgpt.verify_pw3("12345678")?;
|
||||||
|
|
||||||
pws.set_pw1_cds_valid_once(false);
|
pws.set_pw1_cds_valid_once(false);
|
||||||
pws.set_pw1_pin_block(true);
|
pws.set_pw1_pin_block(true);
|
||||||
|
|
||||||
card_tx.set_pw_status_bytes(&pws, false)?;
|
pgpt.set_pw_status_bytes(&pws, false)?;
|
||||||
|
|
||||||
let ard = card_tx.application_related_data()?;
|
let ard = pgpt.application_related_data()?;
|
||||||
let pws = ard.pw_status_bytes()?;
|
let pws = ard.pw_status_bytes()?;
|
||||||
println!("pws {:?}", pws);
|
println!("pws {:?}", pws);
|
||||||
|
|
||||||
|
@ -456,10 +473,10 @@ pub fn test_pw_status(
|
||||||
/// Outputs:
|
/// Outputs:
|
||||||
/// - verify pw3 (check) -> Status
|
/// - verify pw3 (check) -> Status
|
||||||
/// - verify pw1 (check) -> Status
|
/// - verify pw1 (check) -> Status
|
||||||
pub fn test_verify(
|
pub fn test_verify(card: &mut dyn CardBackend, _param: &[&str]) -> Result<TestOutput, TestError> {
|
||||||
card_tx: &mut (dyn CardTransaction + Send + Sync),
|
let mut pgp = OpenPgp::new(card);
|
||||||
_param: &[&str],
|
let mut pgpt = pgp.transaction()?;
|
||||||
) -> Result<TestOutput, TestError> {
|
|
||||||
// Steps:
|
// Steps:
|
||||||
//
|
//
|
||||||
// - try to set name without verify, assert result is not ok
|
// - try to set name without verify, assert result is not ok
|
||||||
|
@ -475,7 +492,7 @@ pub fn test_verify(
|
||||||
let mut out = vec![];
|
let mut out = vec![];
|
||||||
|
|
||||||
// try to set name without verify, assert result is not ok!
|
// try to set name without verify, assert result is not ok!
|
||||||
let res = card_tx.set_name("Notverified<<Hello".as_bytes());
|
let res = pgpt.set_name("Notverified<<Hello".as_bytes());
|
||||||
|
|
||||||
if let Err(Error::CardStatus(s)) = res {
|
if let Err(Error::CardStatus(s)) = res {
|
||||||
assert_eq!(s, StatusBytes::SecurityStatusNotSatisfied);
|
assert_eq!(s, StatusBytes::SecurityStatusNotSatisfied);
|
||||||
|
@ -483,9 +500,9 @@ pub fn test_verify(
|
||||||
panic!("Status should be 'SecurityStatusNotSatisfied'");
|
panic!("Status should be 'SecurityStatusNotSatisfied'");
|
||||||
}
|
}
|
||||||
|
|
||||||
card_tx.verify_pw3("12345678")?;
|
pgpt.verify_pw3("12345678")?;
|
||||||
|
|
||||||
match card_tx.check_pw3() {
|
match pgpt.check_pw3() {
|
||||||
Err(Error::CardStatus(s)) => {
|
Err(Error::CardStatus(s)) => {
|
||||||
// e.g. yubikey5 returns an error status!
|
// e.g. yubikey5 returns an error status!
|
||||||
out.push(TestResult::Status(s));
|
out.push(TestResult::Status(s));
|
||||||
|
@ -496,14 +513,14 @@ pub fn test_verify(
|
||||||
Ok(_) => out.push(TestResult::StatusOk),
|
Ok(_) => out.push(TestResult::StatusOk),
|
||||||
}
|
}
|
||||||
|
|
||||||
card_tx.set_name(b"Admin<<Hello")?;
|
pgpt.set_name(b"Admin<<Hello")?;
|
||||||
|
|
||||||
let cardholder = card_tx.cardholder_related_data()?;
|
let cardholder = pgpt.cardholder_related_data()?;
|
||||||
assert_eq!(cardholder.name(), Some("Admin<<Hello".as_bytes()));
|
assert_eq!(cardholder.name(), Some("Admin<<Hello".as_bytes()));
|
||||||
|
|
||||||
card_tx.verify_pw1("123456")?;
|
pgpt.verify_pw1("123456")?;
|
||||||
|
|
||||||
match card_tx.check_pw3() {
|
match pgpt.check_pw3() {
|
||||||
Err(Error::CardStatus(s)) => {
|
Err(Error::CardStatus(s)) => {
|
||||||
// e.g. yubikey5 returns an error status!
|
// e.g. yubikey5 returns an error status!
|
||||||
out.push(TestResult::Status(s));
|
out.push(TestResult::Status(s));
|
||||||
|
@ -514,37 +531,40 @@ pub fn test_verify(
|
||||||
Ok(_) => out.push(TestResult::StatusOk),
|
Ok(_) => out.push(TestResult::StatusOk),
|
||||||
}
|
}
|
||||||
|
|
||||||
card_tx.set_name(b"There<<Hello")?;
|
pgpt.set_name(b"There<<Hello")?;
|
||||||
|
|
||||||
let cardholder = card_tx.cardholder_related_data()?;
|
let cardholder = pgpt.cardholder_related_data()?;
|
||||||
assert_eq!(cardholder.name(), Some("There<<Hello".as_bytes()));
|
assert_eq!(cardholder.name(), Some("There<<Hello".as_bytes()));
|
||||||
|
|
||||||
Ok(out)
|
Ok(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn test_change_pw(
|
pub fn test_change_pw(
|
||||||
card_tx: &mut (dyn CardTransaction + Send + Sync),
|
card: &mut dyn CardBackend,
|
||||||
_param: &[&str],
|
_param: &[&str],
|
||||||
) -> Result<TestOutput, TestError> {
|
) -> Result<TestOutput, TestError> {
|
||||||
|
let mut pgp = OpenPgp::new(card);
|
||||||
|
let mut pgpt = pgp.transaction()?;
|
||||||
|
|
||||||
let out = vec![];
|
let out = vec![];
|
||||||
|
|
||||||
// first do admin-less pw1 on gnuk
|
// first do admin-less pw1 on gnuk
|
||||||
// (NOTE: Gnuk requires a key to be loaded before allowing pw changes!)
|
// (NOTE: Gnuk requires a key to be loaded before allowing pw changes!)
|
||||||
println!("change pw1");
|
println!("change pw1");
|
||||||
card_tx.change_pw1("123456", "abcdef00")?;
|
pgpt.change_pw1("123456", "abcdef00")?;
|
||||||
|
|
||||||
// also set admin pw, which means pw1 is now only user-pw again, on gnuk
|
// also set admin pw, which means pw1 is now only user-pw again, on gnuk
|
||||||
println!("change pw3");
|
println!("change pw3");
|
||||||
// ca.change_pw3("abcdef00", "abcdefgh")?; // gnuk
|
// ca.change_pw3("abcdef00", "abcdefgh")?; // gnuk
|
||||||
card_tx.change_pw3("12345678", "abcdefgh")?;
|
pgpt.change_pw3("12345678", "abcdefgh")?;
|
||||||
|
|
||||||
println!("change pw1");
|
println!("change pw1");
|
||||||
card_tx.change_pw1("abcdef00", "abcdef")?; // gnuk
|
pgpt.change_pw1("abcdef00", "abcdef")?; // gnuk
|
||||||
|
|
||||||
// ca.change_pw1("123456", "abcdef")?;
|
// ca.change_pw1("123456", "abcdef")?;
|
||||||
|
|
||||||
println!("verify bad pw1");
|
println!("verify bad pw1");
|
||||||
match card_tx.verify_pw1("123456ab") {
|
match pgpt.verify_pw1("123456ab") {
|
||||||
Err(Error::CardStatus(StatusBytes::SecurityStatusNotSatisfied)) => {
|
Err(Error::CardStatus(StatusBytes::SecurityStatusNotSatisfied)) => {
|
||||||
// this is expected
|
// this is expected
|
||||||
}
|
}
|
||||||
|
@ -555,10 +575,10 @@ pub fn test_change_pw(
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("verify good pw1");
|
println!("verify good pw1");
|
||||||
card_tx.verify_pw1("abcdef")?;
|
pgpt.verify_pw1("abcdef")?;
|
||||||
|
|
||||||
println!("verify bad pw3");
|
println!("verify bad pw3");
|
||||||
match card_tx.verify_pw3("00000000") {
|
match pgpt.verify_pw3("00000000") {
|
||||||
Err(Error::CardStatus(StatusBytes::SecurityStatusNotSatisfied)) => {
|
Err(Error::CardStatus(StatusBytes::SecurityStatusNotSatisfied)) => {
|
||||||
// this is expected
|
// this is expected
|
||||||
}
|
}
|
||||||
|
@ -569,34 +589,37 @@ pub fn test_change_pw(
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("verify good pw3");
|
println!("verify good pw3");
|
||||||
card_tx.verify_pw3("abcdefgh")?;
|
pgpt.verify_pw3("abcdefgh")?;
|
||||||
|
|
||||||
println!("change pw3 back to default");
|
println!("change pw3 back to default");
|
||||||
card_tx.change_pw3("abcdefgh", "12345678")?;
|
pgpt.change_pw3("abcdefgh", "12345678")?;
|
||||||
|
|
||||||
println!("change pw1 back to default");
|
println!("change pw1 back to default");
|
||||||
card_tx.change_pw1("abcdef", "123456")?;
|
pgpt.change_pw1("abcdef", "123456")?;
|
||||||
|
|
||||||
Ok(out)
|
Ok(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn test_reset_retry_counter(
|
pub fn test_reset_retry_counter(
|
||||||
card_tx: &mut (dyn CardTransaction + Send + Sync),
|
card: &mut dyn CardBackend,
|
||||||
_param: &[&str],
|
_param: &[&str],
|
||||||
) -> Result<TestOutput, TestError> {
|
) -> Result<TestOutput, TestError> {
|
||||||
|
let mut pgp = OpenPgp::new(card);
|
||||||
|
let mut pgpt = pgp.transaction()?;
|
||||||
|
|
||||||
let out = vec![];
|
let out = vec![];
|
||||||
|
|
||||||
// set pw3, then pw1 (to bring gnuk into non-admin mode)
|
// set pw3, then pw1 (to bring gnuk into non-admin mode)
|
||||||
println!("set pw3");
|
println!("set pw3");
|
||||||
card_tx.change_pw3("12345678", "12345678")?;
|
pgpt.change_pw3("12345678", "12345678")?;
|
||||||
println!("set pw1");
|
println!("set pw1");
|
||||||
card_tx.change_pw1("123456", "123456")?;
|
pgpt.change_pw1("123456", "123456")?;
|
||||||
|
|
||||||
println!("break pw1");
|
println!("break pw1");
|
||||||
let _ = card_tx.verify_pw1("wrong0");
|
let _ = pgpt.verify_pw1("wrong0");
|
||||||
let _ = card_tx.verify_pw1("wrong0");
|
let _ = pgpt.verify_pw1("wrong0");
|
||||||
let _ = card_tx.verify_pw1("wrong0");
|
let _ = pgpt.verify_pw1("wrong0");
|
||||||
let res = card_tx.verify_pw1("wrong0");
|
let res = pgpt.verify_pw1("wrong0");
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Err(Error::CardStatus(StatusBytes::AuthenticationMethodBlocked)) => {
|
Err(Error::CardStatus(StatusBytes::AuthenticationMethodBlocked)) => {
|
||||||
|
@ -615,23 +638,23 @@ pub fn test_reset_retry_counter(
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("verify pw3");
|
println!("verify pw3");
|
||||||
card_tx.verify_pw3("12345678")?;
|
pgpt.verify_pw3("12345678")?;
|
||||||
|
|
||||||
println!("set resetting code");
|
println!("set resetting code");
|
||||||
card_tx.set_resetting_code("abcdefgh".as_bytes().to_vec())?;
|
pgpt.set_resetting_code("abcdefgh".as_bytes().to_vec())?;
|
||||||
|
|
||||||
println!("reset retry counter");
|
println!("reset retry counter");
|
||||||
// ca.reset_retry_counter_pw1("abcdef".as_bytes().to_vec(), None)?;
|
// ca.reset_retry_counter_pw1("abcdef".as_bytes().to_vec(), None)?;
|
||||||
let _res = card_tx.reset_retry_counter_pw1(
|
let _res = pgpt.reset_retry_counter_pw1(
|
||||||
"abcdef".as_bytes().to_vec(),
|
"abcdef".as_bytes().to_vec(),
|
||||||
Some("abcdefgh".as_bytes().to_vec()),
|
Some("abcdefgh".as_bytes().to_vec()),
|
||||||
);
|
);
|
||||||
|
|
||||||
println!("verify good pw1");
|
println!("verify good pw1");
|
||||||
card_tx.verify_pw1("abcdef")?;
|
pgpt.verify_pw1("abcdef")?;
|
||||||
|
|
||||||
println!("verify bad pw1");
|
println!("verify bad pw1");
|
||||||
match card_tx.verify_pw1("00000000") {
|
match pgpt.verify_pw1("00000000") {
|
||||||
Err(Error::CardStatus(StatusBytes::SecurityStatusNotSatisfied)) => {
|
Err(Error::CardStatus(StatusBytes::SecurityStatusNotSatisfied)) => {
|
||||||
// this is expected
|
// this is expected
|
||||||
}
|
}
|
||||||
|
@ -646,11 +669,10 @@ pub fn test_reset_retry_counter(
|
||||||
|
|
||||||
pub fn run_test(
|
pub fn run_test(
|
||||||
tc: &mut TestCardData,
|
tc: &mut TestCardData,
|
||||||
t: fn(&mut (dyn CardTransaction + Send + Sync), &[&str]) -> Result<TestOutput, TestError>,
|
t: fn(&mut (dyn CardBackend), &[&str]) -> Result<TestOutput, TestError>,
|
||||||
param: &[&str],
|
param: &[&str],
|
||||||
) -> Result<TestOutput, TestError> {
|
) -> Result<TestOutput, TestError> {
|
||||||
let mut card = tc.get_card()?;
|
let mut card = tc.get_card()?;
|
||||||
let mut txc = card.transaction().map_err(|e| anyhow!(e))?;
|
|
||||||
|
|
||||||
t(&mut *txc, param)
|
t(&mut *card, param)
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,14 +14,14 @@ use sequoia_openpgp::serialize::stream::{Armorer, Encryptor, LiteralWriter, Mess
|
||||||
use sequoia_openpgp::Cert;
|
use sequoia_openpgp::Cert;
|
||||||
|
|
||||||
use openpgp_card::card_do::KeyGenerationTime;
|
use openpgp_card::card_do::KeyGenerationTime;
|
||||||
use openpgp_card::{CardTransaction, KeyType};
|
use openpgp_card::{KeyType, OpenPgpTransaction};
|
||||||
use openpgp_card_sequoia::sq_util;
|
use openpgp_card_sequoia::sq_util;
|
||||||
use openpgp_card_sequoia::util::vka_as_uploadable_key;
|
use openpgp_card_sequoia::util::vka_as_uploadable_key;
|
||||||
|
|
||||||
pub const SP: &StandardPolicy = &StandardPolicy::new();
|
pub const SP: &StandardPolicy = &StandardPolicy::new();
|
||||||
|
|
||||||
pub(crate) fn upload_subkeys(
|
pub(crate) fn upload_subkeys(
|
||||||
card_tx: &mut dyn CardTransaction,
|
pgpt: &mut OpenPgpTransaction,
|
||||||
cert: &Cert,
|
cert: &Cert,
|
||||||
policy: &dyn Policy,
|
policy: &dyn Policy,
|
||||||
) -> Result<Vec<(String, KeyGenerationTime)>> {
|
) -> Result<Vec<(String, KeyGenerationTime)>> {
|
||||||
|
@ -46,7 +46,7 @@ pub(crate) fn upload_subkeys(
|
||||||
|
|
||||||
// upload key
|
// upload key
|
||||||
let cuk = vka_as_uploadable_key(vka, None);
|
let cuk = vka_as_uploadable_key(vka, None);
|
||||||
card_tx.key_import(cuk, *kt)?;
|
pgpt.key_import(cuk, *kt)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Wiktor Kwapisiewicz <wiktor@metacode.biz>
|
// SPDX-FileCopyrightText: 2021 Wiktor Kwapisiewicz <wiktor@metacode.biz>
|
||||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
|
|
||||||
use openpgp_card::CardBackend;
|
use openpgp_card::OpenPgp;
|
||||||
use openpgp_card_pcsc::PcscBackend;
|
use openpgp_card_pcsc::PcscBackend;
|
||||||
use openpgp_card_sequoia::card::Open;
|
use openpgp_card_sequoia::card::Open;
|
||||||
|
|
||||||
|
@ -23,9 +23,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let cert_file = &args[2];
|
let cert_file = &args[2];
|
||||||
|
|
||||||
let mut card = PcscBackend::open_by_ident(card_ident, None)?;
|
let mut card = PcscBackend::open_by_ident(card_ident, None)?;
|
||||||
let mut txc = card.transaction()?;
|
let mut pgp = OpenPgp::new(&mut card);
|
||||||
|
|
||||||
let mut open = Open::new(&mut *txc)?;
|
let mut open = Open::new(pgp.transaction()?)?;
|
||||||
|
|
||||||
let pin = std::fs::read_to_string(pin_file)?;
|
let pin = std::fs::read_to_string(pin_file)?;
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Wiktor Kwapisiewicz <wiktor@metacode.biz>
|
// SPDX-FileCopyrightText: 2021 Wiktor Kwapisiewicz <wiktor@metacode.biz>
|
||||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
|
|
||||||
use openpgp_card::CardBackend;
|
use openpgp_card::OpenPgp;
|
||||||
use openpgp_card_pcsc::PcscBackend;
|
use openpgp_card_pcsc::PcscBackend;
|
||||||
use openpgp_card_sequoia::card::Open;
|
use openpgp_card_sequoia::card::Open;
|
||||||
|
|
||||||
|
@ -23,18 +23,18 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let cert_file = &args[2];
|
let cert_file = &args[2];
|
||||||
|
|
||||||
let mut card = PcscBackend::open_by_ident(card_ident, None)?;
|
let mut card = PcscBackend::open_by_ident(card_ident, None)?;
|
||||||
let mut txc = card.transaction()?;
|
let mut pgp = OpenPgp::new(&mut card);
|
||||||
|
|
||||||
let mut open = Open::new(&mut *txc)?;
|
let mut open = Open::new(pgp.transaction()?)?;
|
||||||
|
|
||||||
let pin = std::fs::read_to_string(pin_file)?;
|
let pin = std::fs::read_to_string(pin_file)?;
|
||||||
|
|
||||||
open.verify_user_for_signing(&pin)?;
|
open.verify_user_for_signing(&pin)?;
|
||||||
|
|
||||||
let mut user = open.signing_card().unwrap();
|
let mut sign = open.signing_card().unwrap();
|
||||||
|
|
||||||
let cert = Cert::from_file(cert_file)?;
|
let cert = Cert::from_file(cert_file)?;
|
||||||
let s = user.signer(&cert)?;
|
let s = sign.signer(&cert)?;
|
||||||
|
|
||||||
let stdout = std::io::stdout();
|
let stdout = std::io::stdout();
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ use openpgp_card::card_do::{
|
||||||
ExtendedLengthInfo, Fingerprint, HistoricalBytes, KeyGenerationTime, Lang, PWStatusBytes,
|
ExtendedLengthInfo, Fingerprint, HistoricalBytes, KeyGenerationTime, Lang, PWStatusBytes,
|
||||||
SecuritySupportTemplate, Sex,
|
SecuritySupportTemplate, Sex,
|
||||||
};
|
};
|
||||||
use openpgp_card::{CardTransaction, Error, KeySet, KeyType};
|
use openpgp_card::{Error, KeySet, KeyType, OpenPgpTransaction};
|
||||||
|
|
||||||
use crate::decryptor::CardDecryptor;
|
use crate::decryptor::CardDecryptor;
|
||||||
use crate::signer::CardSigner;
|
use crate::signer::CardSigner;
|
||||||
|
@ -27,7 +27,7 @@ use openpgp_card::crypto_data::PublicKeyMaterial;
|
||||||
/// Representation of an opened OpenPGP card in its base state (i.e. no
|
/// Representation of an opened OpenPGP card in its base state (i.e. no
|
||||||
/// passwords have been verified, default authorization applies).
|
/// passwords have been verified, default authorization applies).
|
||||||
pub struct Open<'a> {
|
pub struct Open<'a> {
|
||||||
card_tx: &'a mut (dyn CardTransaction + Send + Sync),
|
opt: OpenPgpTransaction<'a>,
|
||||||
|
|
||||||
// Cache of "application related data".
|
// Cache of "application related data".
|
||||||
//
|
//
|
||||||
|
@ -46,27 +46,28 @@ pub struct Open<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Open<'a> {
|
impl<'a> Open<'a> {
|
||||||
pub fn new(card_tx: &'a mut (dyn CardTransaction + Send + Sync)) -> Result<Self, Error> {
|
pub fn new(mut ca: OpenPgpTransaction<'a>) -> Result<Self, Error> {
|
||||||
let ard = card_tx.application_related_data()?;
|
let ard = ca.application_related_data()?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
card_tx,
|
opt: ca,
|
||||||
ard,
|
ard,
|
||||||
pw1: false,
|
pw1: false,
|
||||||
pw1_sign: false,
|
pw1_sign: false,
|
||||||
pw3: false,
|
pw3: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn feature_pinpad_verify(&mut self) -> bool {
|
pub fn feature_pinpad_verify(&mut self) -> bool {
|
||||||
self.card_tx.feature_pinpad_verify()
|
self.opt.feature_pinpad_verify()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn feature_pinpad_modify(&mut self) -> bool {
|
pub fn feature_pinpad_modify(&mut self) -> bool {
|
||||||
self.card_tx.feature_pinpad_modify()
|
self.opt.feature_pinpad_modify()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn verify_user(&mut self, pin: &str) -> Result<(), Error> {
|
pub fn verify_user(&mut self, pin: &str) -> Result<(), Error> {
|
||||||
let _ = self.card_tx.verify_pw1(pin)?;
|
let _ = self.opt.verify_pw1(pin)?;
|
||||||
self.pw1 = true;
|
self.pw1 = true;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -74,13 +75,13 @@ impl<'a> Open<'a> {
|
||||||
pub fn verify_user_pinpad(&mut self, prompt: &dyn Fn()) -> Result<(), Error> {
|
pub fn verify_user_pinpad(&mut self, prompt: &dyn Fn()) -> Result<(), Error> {
|
||||||
prompt();
|
prompt();
|
||||||
|
|
||||||
let _ = self.card_tx.verify_pw1_pinpad()?;
|
let _ = self.opt.verify_pw1_pinpad()?;
|
||||||
self.pw1 = true;
|
self.pw1 = true;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn verify_user_for_signing(&mut self, pin: &str) -> Result<(), Error> {
|
pub fn verify_user_for_signing(&mut self, pin: &str) -> Result<(), Error> {
|
||||||
let _ = self.card_tx.verify_pw1_for_signing(pin)?;
|
let _ = self.opt.verify_pw1_for_signing(pin)?;
|
||||||
|
|
||||||
// FIXME: depending on card mode, pw1_sign is only usable once
|
// FIXME: depending on card mode, pw1_sign is only usable once
|
||||||
|
|
||||||
|
@ -91,7 +92,7 @@ impl<'a> Open<'a> {
|
||||||
pub fn verify_user_for_signing_pinpad(&mut self, prompt: &dyn Fn()) -> Result<(), Error> {
|
pub fn verify_user_for_signing_pinpad(&mut self, prompt: &dyn Fn()) -> Result<(), Error> {
|
||||||
prompt();
|
prompt();
|
||||||
|
|
||||||
let _ = self.card_tx.verify_pw1_for_signing_pinpad()?;
|
let _ = self.opt.verify_pw1_for_signing_pinpad()?;
|
||||||
|
|
||||||
// FIXME: depending on card mode, pw1_sign is only usable once
|
// FIXME: depending on card mode, pw1_sign is only usable once
|
||||||
|
|
||||||
|
@ -100,7 +101,7 @@ impl<'a> Open<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn verify_admin(&mut self, pin: &str) -> Result<(), Error> {
|
pub fn verify_admin(&mut self, pin: &str) -> Result<(), Error> {
|
||||||
let _ = self.card_tx.verify_pw3(pin)?;
|
let _ = self.opt.verify_pw3(pin)?;
|
||||||
self.pw3 = true;
|
self.pw3 = true;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -108,7 +109,7 @@ impl<'a> Open<'a> {
|
||||||
pub fn verify_admin_pinpad(&mut self, prompt: &dyn Fn()) -> Result<(), Error> {
|
pub fn verify_admin_pinpad(&mut self, prompt: &dyn Fn()) -> Result<(), Error> {
|
||||||
prompt();
|
prompt();
|
||||||
|
|
||||||
let _ = self.card_tx.verify_pw3_pinpad()?;
|
let _ = self.opt.verify_pw3_pinpad()?;
|
||||||
self.pw3 = true;
|
self.pw3 = true;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -117,41 +118,41 @@ impl<'a> Open<'a> {
|
||||||
///
|
///
|
||||||
/// NOTE: on some cards this functionality seems broken.
|
/// NOTE: on some cards this functionality seems broken.
|
||||||
pub fn check_user_verified(&mut self) -> Result<(), Error> {
|
pub fn check_user_verified(&mut self) -> Result<(), Error> {
|
||||||
self.card_tx.check_pw1()
|
self.opt.check_pw1()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ask the card if the admin password has been successfully verified.
|
/// Ask the card if the admin password has been successfully verified.
|
||||||
///
|
///
|
||||||
/// NOTE: on some cards this functionality seems broken.
|
/// NOTE: on some cards this functionality seems broken.
|
||||||
pub fn check_admin_verified(&mut self) -> Result<(), Error> {
|
pub fn check_admin_verified(&mut self) -> Result<(), Error> {
|
||||||
self.card_tx.check_pw3()
|
self.opt.check_pw3()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn change_user_pin(&mut self, old: &str, new: &str) -> Result<(), Error> {
|
pub fn change_user_pin(&mut self, old: &str, new: &str) -> Result<(), Error> {
|
||||||
self.card_tx.change_pw1(old, new)
|
self.opt.change_pw1(old, new)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn change_user_pin_pinpad(&mut self, prompt: &dyn Fn()) -> Result<(), Error> {
|
pub fn change_user_pin_pinpad(&mut self, prompt: &dyn Fn()) -> Result<(), Error> {
|
||||||
prompt();
|
prompt();
|
||||||
self.card_tx.change_pw1_pinpad()
|
self.opt.change_pw1_pinpad()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reset_user_pin(&mut self, rst: &str, new: &str) -> Result<(), Error> {
|
pub fn reset_user_pin(&mut self, rst: &str, new: &str) -> Result<(), Error> {
|
||||||
self.card_tx
|
self.opt
|
||||||
.reset_retry_counter_pw1(new.into(), Some(rst.into()))
|
.reset_retry_counter_pw1(new.into(), Some(rst.into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn change_admin_pin(&mut self, old: &str, new: &str) -> Result<(), Error> {
|
pub fn change_admin_pin(&mut self, old: &str, new: &str) -> Result<(), Error> {
|
||||||
self.card_tx.change_pw3(old, new)
|
self.opt.change_pw3(old, new)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn change_admin_pin_pinpad(&mut self, prompt: &dyn Fn()) -> Result<(), Error> {
|
pub fn change_admin_pin_pinpad(&mut self, prompt: &dyn Fn()) -> Result<(), Error> {
|
||||||
prompt();
|
prompt();
|
||||||
self.card_tx.change_pw3_pinpad()
|
self.opt.change_pw3_pinpad()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a view of the card authenticated for "User" commands.
|
/// Get a view of the card authenticated for "User" commands.
|
||||||
pub fn user_card<'b>(&'a mut self) -> Option<User<'a, 'b>> {
|
pub fn user_card<'b>(&'b mut self) -> Option<User<'a, 'b>> {
|
||||||
if self.pw1 {
|
if self.pw1 {
|
||||||
Some(User { oc: self })
|
Some(User { oc: self })
|
||||||
} else {
|
} else {
|
||||||
|
@ -259,17 +260,17 @@ impl<'a> Open<'a> {
|
||||||
// --- URL (5f50) ---
|
// --- URL (5f50) ---
|
||||||
|
|
||||||
pub fn url(&mut self) -> Result<String> {
|
pub fn url(&mut self) -> Result<String> {
|
||||||
Ok(String::from_utf8_lossy(&self.card_tx.url()?).to_string())
|
Ok(String::from_utf8_lossy(&self.opt.url()?).to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- cardholder related data (65) ---
|
// --- cardholder related data (65) ---
|
||||||
pub fn cardholder_related_data(&mut self) -> Result<CardholderRelatedData> {
|
pub fn cardholder_related_data(&mut self) -> Result<CardholderRelatedData> {
|
||||||
self.card_tx.cardholder_related_data()
|
self.opt.cardholder_related_data()
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- security support template (7a) ---
|
// --- security support template (7a) ---
|
||||||
pub fn security_support_template(&mut self) -> Result<SecuritySupportTemplate> {
|
pub fn security_support_template(&mut self) -> Result<SecuritySupportTemplate> {
|
||||||
self.card_tx.security_support_template()
|
self.opt.security_support_template()
|
||||||
}
|
}
|
||||||
|
|
||||||
// DO "Algorithm Information" (0xFA)
|
// DO "Algorithm Information" (0xFA)
|
||||||
|
@ -283,25 +284,25 @@ impl<'a> Open<'a> {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.card_tx.algorithm_information()
|
self.opt.algorithm_information()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Firmware Version, YubiKey specific (?)
|
/// Firmware Version, YubiKey specific (?)
|
||||||
pub fn firmware_version(&mut self) -> Result<Vec<u8>> {
|
pub fn firmware_version(&mut self) -> Result<Vec<u8>> {
|
||||||
self.card_tx.firmware_version()
|
self.opt.firmware_version()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------
|
// ----------
|
||||||
|
|
||||||
pub fn public_key(&mut self, key_type: KeyType) -> Result<PublicKeyMaterial> {
|
pub fn public_key(&mut self, key_type: KeyType) -> Result<PublicKeyMaterial> {
|
||||||
self.card_tx.public_key(key_type).map_err(|e| e.into())
|
self.opt.public_key(key_type).map_err(|e| e.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------
|
// ----------
|
||||||
|
|
||||||
/// Delete all state on this OpenPGP card
|
/// Delete all state on this OpenPGP card
|
||||||
pub fn factory_reset(&mut self) -> Result<()> {
|
pub fn factory_reset(&mut self) -> Result<()> {
|
||||||
self.card_tx.factory_reset()
|
self.opt.factory_reset()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -311,9 +312,9 @@ pub struct User<'app, 'open> {
|
||||||
oc: &'open mut Open<'app>,
|
oc: &'open mut Open<'app>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl User<'_, '_> {
|
impl<'app, 'open> User<'app, 'open> {
|
||||||
pub fn decryptor(&mut self, cert: &Cert) -> Result<CardDecryptor, Error> {
|
pub fn decryptor(&mut self, cert: &Cert) -> Result<CardDecryptor<'_, 'app>, Error> {
|
||||||
CardDecryptor::new(self.oc.card_tx, cert)
|
CardDecryptor::new(&mut self.oc.opt, cert)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -323,19 +324,19 @@ pub struct Sign<'app, 'open> {
|
||||||
oc: &'open mut Open<'app>,
|
oc: &'open mut Open<'app>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sign<'_, '_> {
|
impl<'app, 'open> Sign<'app, 'open> {
|
||||||
pub fn signer(&mut self, cert: &Cert) -> std::result::Result<CardSigner, Error> {
|
pub fn signer(&mut self, cert: &Cert) -> std::result::Result<CardSigner<'_, 'app>, Error> {
|
||||||
// FIXME: depending on the setting in "PW1 Status byte", only one
|
// FIXME: depending on the setting in "PW1 Status byte", only one
|
||||||
// signature can be made after verification for signing
|
// signature can be made after verification for signing
|
||||||
|
|
||||||
CardSigner::new(self.oc.card_tx, cert)
|
CardSigner::new(&mut self.oc.opt, cert)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn signer_from_pubkey(&mut self, pubkey: PublicKey) -> CardSigner {
|
pub fn signer_from_pubkey(&mut self, pubkey: PublicKey) -> CardSigner<'_, 'app> {
|
||||||
// FIXME: depending on the setting in "PW1 Status byte", only one
|
// FIXME: depending on the setting in "PW1 Status byte", only one
|
||||||
// signature can be made after verification for signing
|
// signature can be made after verification for signing
|
||||||
|
|
||||||
CardSigner::with_pubkey(self.oc.card_tx, pubkey)
|
CardSigner::with_pubkey(&mut self.oc.opt, pubkey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -361,7 +362,7 @@ impl Admin<'_, '_> {
|
||||||
return Err(anyhow!("Invalid char in name").into());
|
return Err(anyhow!("Invalid char in name").into());
|
||||||
};
|
};
|
||||||
|
|
||||||
self.oc.card_tx.set_name(name.as_bytes())
|
self.oc.opt.set_name(name.as_bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_lang(&mut self, lang: &[Lang]) -> Result<(), Error> {
|
pub fn set_lang(&mut self, lang: &[Lang]) -> Result<(), Error> {
|
||||||
|
@ -369,11 +370,11 @@ impl Admin<'_, '_> {
|
||||||
return Err(anyhow!("lang too long").into());
|
return Err(anyhow!("lang too long").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
self.oc.card_tx.set_lang(lang)
|
self.oc.opt.set_lang(lang)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_sex(&mut self, sex: Sex) -> Result<(), Error> {
|
pub fn set_sex(&mut self, sex: Sex) -> Result<(), Error> {
|
||||||
self.oc.card_tx.set_sex(sex)
|
self.oc.opt.set_sex(sex)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_url(&mut self, url: &str) -> Result<(), Error> {
|
pub fn set_url(&mut self, url: &str) -> Result<(), Error> {
|
||||||
|
@ -390,18 +391,18 @@ impl Admin<'_, '_> {
|
||||||
// or if it's within the acceptable length:
|
// or if it's within the acceptable length:
|
||||||
// send the url update to the card.
|
// send the url update to the card.
|
||||||
|
|
||||||
self.oc.card_tx.set_url(url.as_bytes())
|
self.oc.opt.set_url(url.as_bytes())
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!("URL too long").into())
|
Err(anyhow!("URL too long").into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_resetting_code(&mut self, pin: &str) -> Result<(), Error> {
|
pub fn set_resetting_code(&mut self, pin: &str) -> Result<(), Error> {
|
||||||
self.oc.card_tx.set_resetting_code(pin.into())
|
self.oc.opt.set_resetting_code(pin.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reset_user_pin(&mut self, new: &str) -> Result<(), Error> {
|
pub fn reset_user_pin(&mut self, new: &str) -> Result<(), Error> {
|
||||||
self.oc.card_tx.reset_retry_counter_pw1(new.into(), None)
|
self.oc.opt.reset_retry_counter_pw1(new.into(), None)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Upload a ValidErasedKeyAmalgamation to the card as a specific KeyType.
|
/// Upload a ValidErasedKeyAmalgamation to the card as a specific KeyType.
|
||||||
|
@ -414,7 +415,7 @@ impl Admin<'_, '_> {
|
||||||
password: Option<String>,
|
password: Option<String>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let key = vka_as_uploadable_key(vka, password);
|
let key = vka_as_uploadable_key(vka, password);
|
||||||
self.oc.card_tx.key_import(key, key_type)
|
self.oc.opt.key_import(key, key_type)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_key_simple(
|
pub fn generate_key_simple(
|
||||||
|
@ -423,14 +424,13 @@ impl Admin<'_, '_> {
|
||||||
algo: Option<AlgoSimple>,
|
algo: Option<AlgoSimple>,
|
||||||
) -> Result<(PublicKeyMaterial, KeyGenerationTime), Error> {
|
) -> Result<(PublicKeyMaterial, KeyGenerationTime), Error> {
|
||||||
match algo {
|
match algo {
|
||||||
Some(algo) => {
|
Some(algo) => self
|
||||||
self.oc
|
.oc
|
||||||
.card_tx
|
.opt
|
||||||
.generate_key_simple(public_to_fingerprint, key_type, algo)
|
.generate_key_simple(public_to_fingerprint, key_type, algo),
|
||||||
}
|
|
||||||
None => self
|
None => self
|
||||||
.oc
|
.oc
|
||||||
.card_tx
|
.opt
|
||||||
.generate_key(public_to_fingerprint, key_type, None),
|
.generate_key(public_to_fingerprint, key_type, None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,30 +13,30 @@ use openpgp::Cert;
|
||||||
use sequoia_openpgp as openpgp;
|
use sequoia_openpgp as openpgp;
|
||||||
|
|
||||||
use openpgp_card::crypto_data::Cryptogram;
|
use openpgp_card::crypto_data::Cryptogram;
|
||||||
use openpgp_card::{CardTransaction, Error};
|
use openpgp_card::{Error, OpenPgpTransaction};
|
||||||
|
|
||||||
use crate::sq_util;
|
use crate::sq_util;
|
||||||
use crate::PublicKey;
|
use crate::PublicKey;
|
||||||
|
|
||||||
pub struct CardDecryptor<'a> {
|
pub struct CardDecryptor<'a, 'app> {
|
||||||
/// The OpenPGP card (authenticated to allow decryption operations)
|
/// The OpenPGP card (authenticated to allow decryption operations)
|
||||||
card_tx: &'a mut dyn CardTransaction,
|
ca: &'a mut OpenPgpTransaction<'app>,
|
||||||
|
|
||||||
/// The matching public key for the card's decryption key
|
/// The matching public key for the card's decryption key
|
||||||
public: PublicKey,
|
public: PublicKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> CardDecryptor<'a> {
|
impl<'a, 'app> CardDecryptor<'a, 'app> {
|
||||||
/// Try to create a CardDecryptor.
|
/// Try to create a CardDecryptor.
|
||||||
///
|
///
|
||||||
/// An Error is returned if no match between the card's decryption
|
/// An Error is returned if no match between the card's decryption
|
||||||
/// key and a (sub)key of `cert` can be made.
|
/// key and a (sub)key of `cert` can be made.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
card_tx: &'a mut dyn CardTransaction,
|
ca: &'a mut OpenPgpTransaction<'app>,
|
||||||
cert: &Cert,
|
cert: &Cert,
|
||||||
) -> Result<CardDecryptor<'a>, Error> {
|
) -> Result<CardDecryptor<'a, 'app>, Error> {
|
||||||
// Get the fingerprint for the decryption key from the card.
|
// Get the fingerprint for the decryption key from the card.
|
||||||
let ard = card_tx.application_related_data()?;
|
let ard = ca.application_related_data()?;
|
||||||
let fps = ard.fingerprints()?;
|
let fps = ard.fingerprints()?;
|
||||||
let fp = fps.decryption();
|
let fp = fps.decryption();
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ impl<'a> CardDecryptor<'a> {
|
||||||
|
|
||||||
if let Some(eka) = sq_util::get_subkey_by_fingerprint(cert, &fp)? {
|
if let Some(eka) = sq_util::get_subkey_by_fingerprint(cert, &fp)? {
|
||||||
let public = eka.key().clone();
|
let public = eka.key().clone();
|
||||||
Ok(Self { card_tx, public })
|
Ok(Self { ca, public })
|
||||||
} else {
|
} else {
|
||||||
Err(Error::InternalError(anyhow!(
|
Err(Error::InternalError(anyhow!(
|
||||||
"Failed to find (sub)key {} in cert",
|
"Failed to find (sub)key {} in cert",
|
||||||
|
@ -61,7 +61,7 @@ impl<'a> CardDecryptor<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> crypto::Decryptor for CardDecryptor<'a> {
|
impl<'a, 'app> crypto::Decryptor for CardDecryptor<'a, 'app> {
|
||||||
fn public(&self) -> &PublicKey {
|
fn public(&self) -> &PublicKey {
|
||||||
&self.public
|
&self.public
|
||||||
}
|
}
|
||||||
|
@ -80,7 +80,7 @@ impl<'a> crypto::Decryptor for CardDecryptor<'a> {
|
||||||
match (ciphertext, self.public.mpis()) {
|
match (ciphertext, self.public.mpis()) {
|
||||||
(mpi::Ciphertext::RSA { c: ct }, mpi::PublicKey::RSA { .. }) => {
|
(mpi::Ciphertext::RSA { c: ct }, mpi::PublicKey::RSA { .. }) => {
|
||||||
let dm = Cryptogram::RSA(ct.value());
|
let dm = Cryptogram::RSA(ct.value());
|
||||||
let dec = self.card_tx.decipher(dm)?;
|
let dec = self.ca.decipher(dm)?;
|
||||||
|
|
||||||
let sk = openpgp::crypto::SessionKey::from(&dec[..]);
|
let sk = openpgp::crypto::SessionKey::from(&dec[..]);
|
||||||
Ok(sk)
|
Ok(sk)
|
||||||
|
@ -101,7 +101,7 @@ impl<'a> crypto::Decryptor for CardDecryptor<'a> {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Decryption operation on the card
|
// Decryption operation on the card
|
||||||
let mut dec = self.card_tx.decipher(dm)?;
|
let mut dec = self.ca.decipher(dm)?;
|
||||||
|
|
||||||
// Specifically handle return value format like Gnuk's
|
// Specifically handle return value format like Gnuk's
|
||||||
// (Gnuk returns a leading '0x04' byte and
|
// (Gnuk returns a leading '0x04' byte and
|
||||||
|
@ -129,7 +129,7 @@ impl<'a> crypto::Decryptor for CardDecryptor<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> DecryptionHelper for CardDecryptor<'a> {
|
impl<'a, 'app> DecryptionHelper for CardDecryptor<'a, 'app> {
|
||||||
fn decrypt<D>(
|
fn decrypt<D>(
|
||||||
&mut self,
|
&mut self,
|
||||||
pkesks: &[packet::PKESK],
|
pkesks: &[packet::PKESK],
|
||||||
|
@ -159,7 +159,7 @@ impl<'a> DecryptionHelper for CardDecryptor<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> VerificationHelper for CardDecryptor<'a> {
|
impl VerificationHelper for CardDecryptor<'_, '_> {
|
||||||
fn get_certs(&mut self, _ids: &[openpgp::KeyHandle]) -> openpgp::Result<Vec<openpgp::Cert>> {
|
fn get_certs(&mut self, _ids: &[openpgp::KeyHandle]) -> openpgp::Result<Vec<openpgp::Cert>> {
|
||||||
Ok(vec![])
|
Ok(vec![])
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,13 +13,13 @@
|
||||||
//!
|
//!
|
||||||
//! ```no_run
|
//! ```no_run
|
||||||
//! use openpgp_card_pcsc::PcscBackend;
|
//! use openpgp_card_pcsc::PcscBackend;
|
||||||
//! use openpgp_card::CardBackend;
|
//! use openpgp_card::OpenPgp;
|
||||||
//! use openpgp_card_sequoia::card::Open;
|
//! use openpgp_card_sequoia::card::Open;
|
||||||
//!
|
//!
|
||||||
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
//! for mut card in PcscBackend::cards(None)? {
|
//! for mut card in PcscBackend::cards(None)? {
|
||||||
//! let mut txc = card.transaction()?;
|
//! let mut pgp = OpenPgp::new(&mut card);
|
||||||
//! let open = Open::new(&mut *txc)?;
|
//! let mut open = Open::new(pgp.transaction()?)?;
|
||||||
//! println!("Found OpenPGP card with ident '{}'",
|
//! println!("Found OpenPGP card with ident '{}'",
|
||||||
//! open.application_identifier()?.ident());
|
//! open.application_identifier()?.ident());
|
||||||
//! }
|
//! }
|
||||||
|
@ -31,13 +31,13 @@
|
||||||
//!
|
//!
|
||||||
//! ```no_run
|
//! ```no_run
|
||||||
//! use openpgp_card_pcsc::PcscBackend;
|
//! use openpgp_card_pcsc::PcscBackend;
|
||||||
//! use openpgp_card::CardBackend;
|
//! use openpgp_card::OpenPgp;
|
||||||
//! use openpgp_card_sequoia::card::Open;
|
//! use openpgp_card_sequoia::card::Open;
|
||||||
//!
|
//!
|
||||||
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
//! let mut card = PcscBackend::open_by_ident("abcd:12345678", None)?;
|
//! let mut card = PcscBackend::open_by_ident("abcd:12345678", None)?;
|
||||||
//! let mut txc = card.transaction()?;
|
//! let mut pgp = OpenPgp::new(&mut card);
|
||||||
//! let mut open = Open::new(&mut *txc)?;
|
//! let mut open = Open::new(pgp.transaction()?)?;
|
||||||
//! # Ok(())
|
//! # Ok(())
|
||||||
//! # }
|
//! # }
|
||||||
//! ```
|
//! ```
|
||||||
|
@ -53,15 +53,15 @@
|
||||||
//!
|
//!
|
||||||
//! ```no_run
|
//! ```no_run
|
||||||
//! use openpgp_card_pcsc::PcscBackend;
|
//! use openpgp_card_pcsc::PcscBackend;
|
||||||
//! use openpgp_card::CardBackend;
|
//! use openpgp_card::OpenPgp;
|
||||||
//! use openpgp_card_sequoia::card::Open;
|
//! use openpgp_card_sequoia::card::Open;
|
||||||
//!
|
//!
|
||||||
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
//! // Open card via PCSC
|
//! // Open card via PCSC
|
||||||
//! use sequoia_openpgp::policy::StandardPolicy;
|
//! use sequoia_openpgp::policy::StandardPolicy;
|
||||||
//! let mut card = PcscBackend::open_by_ident("abcd:12345678", None)?;
|
//! let mut card = PcscBackend::open_by_ident("abcd:12345678", None)?;
|
||||||
//! let mut txc = card.transaction()?;
|
//! let mut pgp = OpenPgp::new(&mut card);
|
||||||
//! let mut open = Open::new(&mut *txc)?;
|
//! let mut open = Open::new(pgp.transaction()?)?;
|
||||||
//!
|
//!
|
||||||
//! // Get authorization for user access to the card with password
|
//! // Get authorization for user access to the card with password
|
||||||
//! open.verify_user("123456")?;
|
//! open.verify_user("123456")?;
|
||||||
|
@ -96,15 +96,15 @@
|
||||||
//!
|
//!
|
||||||
//! ```no_run
|
//! ```no_run
|
||||||
//! use openpgp_card_pcsc::PcscBackend;
|
//! use openpgp_card_pcsc::PcscBackend;
|
||||||
//! use openpgp_card::CardBackend;
|
//! use openpgp_card::OpenPgp;
|
||||||
//! use openpgp_card_sequoia::card::Open;
|
//! use openpgp_card_sequoia::card::Open;
|
||||||
//!
|
//!
|
||||||
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
//! // Open card via PCSC
|
//! // Open card via PCSC
|
||||||
//! use sequoia_openpgp::policy::StandardPolicy;
|
//! use sequoia_openpgp::policy::StandardPolicy;
|
||||||
//! let mut card = PcscBackend::open_by_ident("abcd:12345678", None)?;
|
//! let mut card = PcscBackend::open_by_ident("abcd:12345678", None)?;
|
||||||
//! let mut txc = card.transaction()?;
|
//! let mut pgp = OpenPgp::new(&mut card);
|
||||||
//! let mut open = Open::new(&mut *txc)?;
|
//! let mut open = Open::new(pgp.transaction()?)?;
|
||||||
//!
|
//!
|
||||||
//! // Get authorization for signing access to the card with password
|
//! // Get authorization for signing access to the card with password
|
||||||
//! open.verify_user_for_signing("123456")?;
|
//! open.verify_user_for_signing("123456")?;
|
||||||
|
@ -129,14 +129,14 @@
|
||||||
//!
|
//!
|
||||||
//! ```no_run
|
//! ```no_run
|
||||||
//! use openpgp_card_pcsc::PcscBackend;
|
//! use openpgp_card_pcsc::PcscBackend;
|
||||||
//! use openpgp_card::CardBackend;
|
//! use openpgp_card::OpenPgp;
|
||||||
//! use openpgp_card_sequoia::card::Open;
|
//! use openpgp_card_sequoia::card::Open;
|
||||||
//!
|
//!
|
||||||
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
//! // Open card via PCSC
|
//! // Open card via PCSC
|
||||||
//! let mut card = PcscBackend::open_by_ident("abcd:12345678", None)?;
|
//! let mut card = PcscBackend::open_by_ident("abcd:12345678", None)?;
|
||||||
//! let mut txc = card.transaction()?;
|
//! let mut pgp = OpenPgp::new(&mut card);
|
||||||
//! let mut open = Open::new(&mut *txc)?;
|
//! let mut open = Open::new(pgp.transaction()?)?;
|
||||||
//!
|
//!
|
||||||
//! // Get authorization for admin access to the card with password
|
//! // Get authorization for admin access to the card with password
|
||||||
//! open.verify_admin("12345678")?;
|
//! open.verify_admin("12345678")?;
|
||||||
|
|
|
@ -10,7 +10,7 @@ use sequoia_openpgp::policy::StandardPolicy;
|
||||||
use sequoia_openpgp::Cert;
|
use sequoia_openpgp::Cert;
|
||||||
|
|
||||||
use openpgp_card::card_do::Sex;
|
use openpgp_card::card_do::Sex;
|
||||||
use openpgp_card::{CardBackend, KeyType};
|
use openpgp_card::{KeyType, OpenPgp};
|
||||||
use openpgp_card_pcsc::PcscBackend;
|
use openpgp_card_pcsc::PcscBackend;
|
||||||
|
|
||||||
use openpgp_card_sequoia::card::Open;
|
use openpgp_card_sequoia::card::Open;
|
||||||
|
@ -36,9 +36,9 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
|
||||||
if let Ok(test_card_ident) = test_card_ident {
|
if let Ok(test_card_ident) = test_card_ident {
|
||||||
let mut card = PcscBackend::open_by_ident(&test_card_ident, None)?;
|
let mut card = PcscBackend::open_by_ident(&test_card_ident, None)?;
|
||||||
let mut txc = card.transaction()?;
|
let mut pgp = OpenPgp::new(&mut card);
|
||||||
|
|
||||||
let mut open = Open::new(&mut *txc)?;
|
let mut open = Open::new(pgp.transaction()?)?;
|
||||||
|
|
||||||
// card metadata
|
// card metadata
|
||||||
|
|
||||||
|
@ -141,9 +141,9 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
// Open fresh Card for decrypt
|
// Open fresh Card for decrypt
|
||||||
// -----------------------------
|
// -----------------------------
|
||||||
let mut card = PcscBackend::open_by_ident(&test_card_ident, None)?;
|
let mut card = PcscBackend::open_by_ident(&test_card_ident, None)?;
|
||||||
let mut txc = card.transaction()?;
|
let mut pgp = OpenPgp::new(&mut card);
|
||||||
|
|
||||||
let mut open = Open::new(&mut *txc)?;
|
let mut open = Open::new(pgp.transaction()?)?;
|
||||||
|
|
||||||
// Check that we're still using the expected card
|
// Check that we're still using the expected card
|
||||||
let app_id = open.application_identifier()?;
|
let app_id = open.application_identifier()?;
|
||||||
|
@ -181,9 +181,9 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
// Open fresh Card for signing
|
// Open fresh Card for signing
|
||||||
// -----------------------------
|
// -----------------------------
|
||||||
let mut card = PcscBackend::open_by_ident(&test_card_ident, None)?;
|
let mut card = PcscBackend::open_by_ident(&test_card_ident, None)?;
|
||||||
let mut txc = card.transaction()?;
|
let mut pgp = OpenPgp::new(&mut card);
|
||||||
|
|
||||||
let mut open = Open::new(&mut *txc)?;
|
let mut open = Open::new(pgp.transaction()?)?;
|
||||||
|
|
||||||
// Sign
|
// Sign
|
||||||
open.verify_user_for_signing("123456")?;
|
open.verify_user_for_signing("123456")?;
|
||||||
|
@ -213,9 +213,10 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
println!("The following OpenPGP cards are connected to your system:");
|
println!("The following OpenPGP cards are connected to your system:");
|
||||||
|
|
||||||
for mut card in PcscBackend::cards(None)? {
|
for mut card in PcscBackend::cards(None)? {
|
||||||
let mut txc = card.transaction()?;
|
let mut pgp = OpenPgp::new(&mut card);
|
||||||
|
|
||||||
|
let open = Open::new(pgp.transaction()?)?;
|
||||||
|
|
||||||
let open = Open::new(&mut *txc)?;
|
|
||||||
println!(" {}", open.application_identifier()?.ident());
|
println!(" {}", open.application_identifier()?.ident());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,30 +11,30 @@ use openpgp::types::{Curve, PublicKeyAlgorithm};
|
||||||
use sequoia_openpgp as openpgp;
|
use sequoia_openpgp as openpgp;
|
||||||
|
|
||||||
use openpgp_card::crypto_data::Hash;
|
use openpgp_card::crypto_data::Hash;
|
||||||
use openpgp_card::{CardTransaction, Error};
|
use openpgp_card::{Error, OpenPgpTransaction};
|
||||||
|
|
||||||
use crate::sq_util;
|
use crate::sq_util;
|
||||||
use crate::PublicKey;
|
use crate::PublicKey;
|
||||||
|
|
||||||
pub struct CardSigner<'a> {
|
pub struct CardSigner<'a, 'app> {
|
||||||
/// The OpenPGP card (authenticated to allow signing operations)
|
/// The OpenPGP card (authenticated to allow signing operations)
|
||||||
card_tx: &'a mut (dyn CardTransaction + Send + Sync),
|
ca: &'a mut OpenPgpTransaction<'app>,
|
||||||
|
|
||||||
/// The matching public key for the card's signing key
|
/// The matching public key for the card's signing key
|
||||||
public: PublicKey,
|
public: PublicKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> CardSigner<'a> {
|
impl<'a, 'app> CardSigner<'a, 'app> {
|
||||||
/// Try to create a CardSigner.
|
/// Try to create a CardSigner.
|
||||||
///
|
///
|
||||||
/// An Error is returned if no match between the card's signing
|
/// An Error is returned if no match between the card's signing
|
||||||
/// key and a (sub)key of `cert` can be made.
|
/// key and a (sub)key of `cert` can be made.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
card_tx: &'a mut (dyn CardTransaction + Send + Sync),
|
ca: &'a mut OpenPgpTransaction<'app>,
|
||||||
cert: &openpgp::Cert,
|
cert: &openpgp::Cert,
|
||||||
) -> Result<CardSigner<'a>, Error> {
|
) -> Result<CardSigner<'a, 'app>, Error> {
|
||||||
// Get the fingerprint for the signing key from the card.
|
// Get the fingerprint for the signing key from the card.
|
||||||
let ard = card_tx.application_related_data()?;
|
let ard = ca.application_related_data()?;
|
||||||
let fps = ard.fingerprints()?;
|
let fps = ard.fingerprints()?;
|
||||||
let fp = fps.signature();
|
let fp = fps.signature();
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ impl<'a> CardSigner<'a> {
|
||||||
|
|
||||||
if let Some(eka) = sq_util::get_subkey_by_fingerprint(cert, &fp)? {
|
if let Some(eka) = sq_util::get_subkey_by_fingerprint(cert, &fp)? {
|
||||||
let key = eka.key().clone();
|
let key = eka.key().clone();
|
||||||
Ok(Self::with_pubkey(card_tx, key))
|
Ok(Self::with_pubkey(ca, key))
|
||||||
} else {
|
} else {
|
||||||
Err(Error::InternalError(anyhow!(
|
Err(Error::InternalError(anyhow!(
|
||||||
"Failed to find (sub)key {} in cert",
|
"Failed to find (sub)key {} in cert",
|
||||||
|
@ -59,14 +59,14 @@ impl<'a> CardSigner<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn with_pubkey(
|
pub(crate) fn with_pubkey(
|
||||||
card_tx: &'a mut (dyn CardTransaction + Send + Sync),
|
ca: &'a mut OpenPgpTransaction<'app>,
|
||||||
public: PublicKey,
|
public: PublicKey,
|
||||||
) -> CardSigner<'a> {
|
) -> CardSigner<'a, 'app> {
|
||||||
CardSigner { card_tx, public }
|
CardSigner { ca, public }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> crypto::Signer for CardSigner<'a> {
|
impl<'a, 'app> crypto::Signer for CardSigner<'a, 'app> {
|
||||||
fn public(&self) -> &PublicKey {
|
fn public(&self) -> &PublicKey {
|
||||||
&self.public
|
&self.public
|
||||||
}
|
}
|
||||||
|
@ -111,7 +111,7 @@ impl<'a> crypto::Signer for CardSigner<'a> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let sig = self.card_tx.signature_for_hash(hash)?;
|
let sig = self.ca.signature_for_hash(hash)?;
|
||||||
|
|
||||||
let mpi = mpi::MPI::new(&sig[..]);
|
let mpi = mpi::MPI::new(&sig[..]);
|
||||||
Ok(mpi::Signature::RSA { s: mpi })
|
Ok(mpi::Signature::RSA { s: mpi })
|
||||||
|
@ -119,7 +119,7 @@ impl<'a> crypto::Signer for CardSigner<'a> {
|
||||||
(PublicKeyAlgorithm::EdDSA, mpi::PublicKey::EdDSA { .. }) => {
|
(PublicKeyAlgorithm::EdDSA, mpi::PublicKey::EdDSA { .. }) => {
|
||||||
let hash = Hash::EdDSA(digest);
|
let hash = Hash::EdDSA(digest);
|
||||||
|
|
||||||
let sig = self.card_tx.signature_for_hash(hash)?;
|
let sig = self.ca.signature_for_hash(hash)?;
|
||||||
|
|
||||||
let r = mpi::MPI::new(&sig[..32]);
|
let r = mpi::MPI::new(&sig[..32]);
|
||||||
let s = mpi::MPI::new(&sig[32..]);
|
let s = mpi::MPI::new(&sig[32..]);
|
||||||
|
@ -134,7 +134,7 @@ impl<'a> crypto::Signer for CardSigner<'a> {
|
||||||
_ => Hash::ECDSA(digest),
|
_ => Hash::ECDSA(digest),
|
||||||
};
|
};
|
||||||
|
|
||||||
let sig = self.card_tx.signature_for_hash(hash)?;
|
let sig = self.ca.signature_for_hash(hash)?;
|
||||||
|
|
||||||
let len_2 = sig.len() / 2;
|
let len_2 = sig.len() / 2;
|
||||||
let r = mpi::MPI::new(&sig[..len_2]);
|
let r = mpi::MPI::new(&sig[..len_2]);
|
||||||
|
|
|
@ -27,7 +27,7 @@ use sequoia_openpgp as openpgp;
|
||||||
use openpgp_card::algorithm::{Algo, Curve};
|
use openpgp_card::algorithm::{Algo, Curve};
|
||||||
use openpgp_card::card_do::{Fingerprint, KeyGenerationTime};
|
use openpgp_card::card_do::{Fingerprint, KeyGenerationTime};
|
||||||
use openpgp_card::crypto_data::{CardUploadableKey, PublicKeyMaterial};
|
use openpgp_card::crypto_data::{CardUploadableKey, PublicKeyMaterial};
|
||||||
use openpgp_card::{CardTransaction, Error, KeyType};
|
use openpgp_card::{Error, KeyType, OpenPgpTransaction};
|
||||||
|
|
||||||
use crate::card::Open;
|
use crate::card::Open;
|
||||||
use crate::privkey::SequoiaKey;
|
use crate::privkey::SequoiaKey;
|
||||||
|
@ -283,7 +283,7 @@ pub fn vka_as_uploadable_key(
|
||||||
|
|
||||||
/// FIXME: this fn is used in card_functionality, but should be removed
|
/// FIXME: this fn is used in card_functionality, but should be removed
|
||||||
pub fn sign(
|
pub fn sign(
|
||||||
card_tx: &mut (dyn CardTransaction + Send + Sync),
|
card_tx: &'_ mut OpenPgpTransaction<'_>,
|
||||||
cert: &Cert,
|
cert: &Cert,
|
||||||
input: &mut dyn io::Read,
|
input: &mut dyn io::Read,
|
||||||
) -> Result<String> {
|
) -> Result<String> {
|
||||||
|
@ -307,7 +307,7 @@ pub fn sign(
|
||||||
|
|
||||||
/// FIXME: this fn is used in card_functionality, but should be removed
|
/// FIXME: this fn is used in card_functionality, but should be removed
|
||||||
pub fn decrypt(
|
pub fn decrypt(
|
||||||
card_tx: &mut dyn CardTransaction,
|
card_tx: &'_ mut OpenPgpTransaction<'_>,
|
||||||
cert: &Cert,
|
cert: &Cert,
|
||||||
msg: Vec<u8>,
|
msg: Vec<u8>,
|
||||||
p: &dyn Policy,
|
p: &dyn Policy,
|
||||||
|
|
|
@ -15,9 +15,9 @@ use crate::crypto_data::{
|
||||||
CardUploadableKey, EccKey, EccPub, EccType, PrivateKeyMaterial, PublicKeyMaterial, RSAKey,
|
CardUploadableKey, EccKey, EccPub, EccType, PrivateKeyMaterial, PublicKeyMaterial, RSAKey,
|
||||||
RSAPub,
|
RSAPub,
|
||||||
};
|
};
|
||||||
|
use crate::openpgp::OpenPgpTransaction;
|
||||||
use crate::tlv::{length::tlv_encode_length, value::Value, Tlv};
|
use crate::tlv::{length::tlv_encode_length, value::Value, Tlv};
|
||||||
use crate::{apdu, KeyType};
|
use crate::{apdu, Error, KeyType};
|
||||||
use crate::{CardTransaction, Error};
|
|
||||||
|
|
||||||
/// Generate asymmetric key pair on the card.
|
/// Generate asymmetric key pair on the card.
|
||||||
///
|
///
|
||||||
|
@ -29,15 +29,12 @@ use crate::{CardTransaction, Error};
|
||||||
///
|
///
|
||||||
/// `fp_from_pub` calculates the fingerprint for a public key data object and
|
/// `fp_from_pub` calculates the fingerprint for a public key data object and
|
||||||
/// creation timestamp
|
/// creation timestamp
|
||||||
pub(crate) fn gen_key_with_metadata<C>(
|
pub(crate) fn gen_key_with_metadata(
|
||||||
card_tx: &mut C,
|
card_tx: &mut OpenPgpTransaction,
|
||||||
fp_from_pub: fn(&PublicKeyMaterial, KeyGenerationTime, KeyType) -> Result<Fingerprint, Error>,
|
fp_from_pub: fn(&PublicKeyMaterial, KeyGenerationTime, KeyType) -> Result<Fingerprint, Error>,
|
||||||
key_type: KeyType,
|
key_type: KeyType,
|
||||||
algo: Option<&Algo>,
|
algo: Option<&Algo>,
|
||||||
) -> Result<(PublicKeyMaterial, KeyGenerationTime), Error>
|
) -> Result<(PublicKeyMaterial, KeyGenerationTime), Error> {
|
||||||
where
|
|
||||||
C: CardTransaction + ?Sized,
|
|
||||||
{
|
|
||||||
// Set algo on card if it's Some
|
// Set algo on card if it's Some
|
||||||
if let Some(target_algo) = algo {
|
if let Some(target_algo) = algo {
|
||||||
// FIXME: caching
|
// FIXME: caching
|
||||||
|
@ -125,18 +122,15 @@ fn tlv_to_pubkey(tlv: &Tlv, algo: &Algo) -> Result<PublicKeyMaterial> {
|
||||||
///
|
///
|
||||||
/// This runs the low level key generation primitive on the card.
|
/// This runs the low level key generation primitive on the card.
|
||||||
/// (This does not set algorithm attributes, creation time or fingerprint)
|
/// (This does not set algorithm attributes, creation time or fingerprint)
|
||||||
pub(crate) fn generate_asymmetric_key_pair<C>(
|
pub(crate) fn generate_asymmetric_key_pair(
|
||||||
card_tx: &mut C,
|
card_tx: &mut OpenPgpTransaction,
|
||||||
key_type: KeyType,
|
key_type: KeyType,
|
||||||
) -> Result<Tlv, Error>
|
) -> Result<Tlv, Error> {
|
||||||
where
|
|
||||||
C: CardTransaction + ?Sized,
|
|
||||||
{
|
|
||||||
// generate key
|
// generate key
|
||||||
let crt = control_reference_template(key_type)?;
|
let crt = control_reference_template(key_type)?;
|
||||||
let gen_key_cmd = commands::gen_key(crt.serialize().to_vec());
|
let gen_key_cmd = commands::gen_key(crt.serialize().to_vec());
|
||||||
|
|
||||||
let resp = apdu::send_command(card_tx, gen_key_cmd, true)?;
|
let resp = apdu::send_command(card_tx.tx(), gen_key_cmd, true)?;
|
||||||
resp.check_ok()?;
|
resp.check_ok()?;
|
||||||
|
|
||||||
let tlv = Tlv::try_from(resp.data()?)?;
|
let tlv = Tlv::try_from(resp.data()?)?;
|
||||||
|
@ -150,10 +144,10 @@ where
|
||||||
/// in the card or imported")
|
/// in the card or imported")
|
||||||
///
|
///
|
||||||
/// (See 7.2.14 GENERATE ASYMMETRIC KEY PAIR)
|
/// (See 7.2.14 GENERATE ASYMMETRIC KEY PAIR)
|
||||||
pub(crate) fn public_key<C>(card_tx: &mut C, key_type: KeyType) -> Result<PublicKeyMaterial, Error>
|
pub(crate) fn public_key(
|
||||||
where
|
card_tx: &mut OpenPgpTransaction,
|
||||||
C: CardTransaction + ?Sized,
|
key_type: KeyType,
|
||||||
{
|
) -> Result<PublicKeyMaterial, Error> {
|
||||||
// get current algo
|
// get current algo
|
||||||
let ard = card_tx.application_related_data()?; // FIXME: caching
|
let ard = card_tx.application_related_data()?; // FIXME: caching
|
||||||
let algo = ard.algorithm_attributes(key_type)?;
|
let algo = ard.algorithm_attributes(key_type)?;
|
||||||
|
@ -162,7 +156,7 @@ where
|
||||||
let crt = control_reference_template(key_type)?;
|
let crt = control_reference_template(key_type)?;
|
||||||
let get_pub_key_cmd = commands::get_pub_key(crt.serialize().to_vec());
|
let get_pub_key_cmd = commands::get_pub_key(crt.serialize().to_vec());
|
||||||
|
|
||||||
let resp = apdu::send_command(card_tx, get_pub_key_cmd, true)?;
|
let resp = apdu::send_command(card_tx.tx(), get_pub_key_cmd, true)?;
|
||||||
resp.check_ok()?;
|
resp.check_ok()?;
|
||||||
|
|
||||||
let tlv = Tlv::try_from(resp.data()?)?;
|
let tlv = Tlv::try_from(resp.data()?)?;
|
||||||
|
@ -176,15 +170,12 @@ where
|
||||||
/// If the key is suitable for `key_type`, an Error is returned (either
|
/// If the key is suitable for `key_type`, an Error is returned (either
|
||||||
/// caused by checks before attempting to upload the key to the card, or by
|
/// caused by checks before attempting to upload the key to the card, or by
|
||||||
/// an error that the card reports during an attempt to upload the key).
|
/// an error that the card reports during an attempt to upload the key).
|
||||||
pub(crate) fn key_import<C>(
|
pub(crate) fn key_import(
|
||||||
card_tx: &mut C,
|
card_tx: &mut OpenPgpTransaction,
|
||||||
key: Box<dyn CardUploadableKey>,
|
key: Box<dyn CardUploadableKey>,
|
||||||
key_type: KeyType,
|
key_type: KeyType,
|
||||||
algo_info: Option<AlgoInfo>,
|
algo_info: Option<AlgoInfo>,
|
||||||
) -> Result<(), Error>
|
) -> Result<(), Error> {
|
||||||
where
|
|
||||||
C: CardTransaction + ?Sized,
|
|
||||||
{
|
|
||||||
// FIXME: caching?
|
// FIXME: caching?
|
||||||
let ard = card_tx.application_related_data()?;
|
let ard = card_tx.application_related_data()?;
|
||||||
|
|
||||||
|
@ -220,7 +211,7 @@ where
|
||||||
card_tx.set_algorithm_attributes(key_type, &algo)?;
|
card_tx.set_algorithm_attributes(key_type, &algo)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
apdu::send_command(card_tx, key_cmd, false)?.check_ok()?;
|
apdu::send_command(card_tx.tx(), key_cmd, false)?.check_ok()?;
|
||||||
card_tx.set_fingerprint(fp, key_type)?;
|
card_tx.set_fingerprint(fp, key_type)?;
|
||||||
card_tx.set_creation_time(key.timestamp(), key_type)?;
|
card_tx.set_creation_time(key.timestamp(), key_type)?;
|
||||||
|
|
||||||
|
|
|
@ -31,27 +31,24 @@ pub mod card_do;
|
||||||
pub mod crypto_data;
|
pub mod crypto_data;
|
||||||
mod errors;
|
mod errors;
|
||||||
pub(crate) mod keys;
|
pub(crate) mod keys;
|
||||||
|
mod openpgp;
|
||||||
mod tlv;
|
mod tlv;
|
||||||
|
|
||||||
pub use crate::errors::{Error, SmartcardError, StatusBytes};
|
pub use crate::errors::{Error, SmartcardError, StatusBytes};
|
||||||
|
pub use crate::openpgp::{OpenPgp, OpenPgpTransaction};
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::Result;
|
||||||
use std::convert::TryFrom;
|
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
use crate::algorithm::{Algo, AlgoInfo, AlgoSimple};
|
|
||||||
use crate::apdu::commands;
|
use crate::apdu::commands;
|
||||||
use crate::apdu::response::RawResponse;
|
use crate::card_do::ApplicationRelatedData;
|
||||||
use crate::card_do::{
|
use crate::tlv::{tag::Tag, value::Value, Tlv};
|
||||||
ApplicationRelatedData, CardholderRelatedData, Fingerprint, KeyGenerationTime, Lang,
|
|
||||||
PWStatusBytes, SecuritySupportTemplate, Sex,
|
|
||||||
};
|
|
||||||
use crate::crypto_data::{CardUploadableKey, Cryptogram, Hash, PublicKeyMaterial};
|
|
||||||
use crate::tlv::tag::Tag;
|
|
||||||
use crate::tlv::value::Value;
|
|
||||||
use crate::tlv::Tlv;
|
|
||||||
|
|
||||||
|
/// The CardBackend trait defines a connection with an OpenPGP card via a
|
||||||
|
/// backend implementation (e.g. via the pcsc backend in the crate
|
||||||
|
/// [openpgp-card-pcsc](https://crates.io/crates/openpgp-card-pcsc)),
|
||||||
|
/// A CardBackend is only used to get access to a `CardTransaction` object.
|
||||||
#[blanket::blanket(derive(Box))]
|
#[blanket::blanket(derive(Box))]
|
||||||
pub trait CardBackend {
|
pub trait CardBackend {
|
||||||
fn transaction(&mut self) -> Result<Box<dyn CardTransaction + Send + Sync + '_>, Error>;
|
fn transaction(&mut self) -> Result<Box<dyn CardTransaction + Send + Sync + '_>, Error>;
|
||||||
|
@ -61,8 +58,6 @@ pub trait CardBackend {
|
||||||
/// backend implementation (e.g. the pcsc backend in the crate
|
/// backend implementation (e.g. the pcsc backend in the crate
|
||||||
/// [openpgp-card-pcsc](https://crates.io/crates/openpgp-card-pcsc)),
|
/// [openpgp-card-pcsc](https://crates.io/crates/openpgp-card-pcsc)),
|
||||||
/// after opening a transaction from a CardBackend.
|
/// after opening a transaction from a CardBackend.
|
||||||
///
|
|
||||||
/// CardTransaction exposes low-level access to OpenPGP card functionality.
|
|
||||||
#[blanket::blanket(derive(Box))]
|
#[blanket::blanket(derive(Box))]
|
||||||
pub trait CardTransaction {
|
pub trait CardTransaction {
|
||||||
/// Transmit the command data in `cmd` to the card.
|
/// Transmit the command data in `cmd` to the card.
|
||||||
|
@ -110,6 +105,21 @@ pub trait CardTransaction {
|
||||||
apdu::send_command(self, select_openpgp, false)?.try_into()
|
apdu::send_command(self, select_openpgp, false)?.try_into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the "application related data" from the card.
|
||||||
|
///
|
||||||
|
/// (This data should probably be cached in a higher layer. Some parts of
|
||||||
|
/// it are needed regularly, and it does not usually change during
|
||||||
|
/// normal use of a card.)
|
||||||
|
fn application_related_data(&mut self) -> Result<ApplicationRelatedData> {
|
||||||
|
let ad = commands::application_related_data();
|
||||||
|
let resp = apdu::send_command(self, ad, true)?;
|
||||||
|
let value = Value::from(resp.data()?, true)?;
|
||||||
|
|
||||||
|
log::debug!(" ARD value: {:x?}", value);
|
||||||
|
|
||||||
|
Ok(ApplicationRelatedData(Tlv::new(Tag::from([0x6E]), value)))
|
||||||
|
}
|
||||||
|
|
||||||
/// Get a CardApp based on a CardTransaction.
|
/// Get a CardApp based on a CardTransaction.
|
||||||
///
|
///
|
||||||
/// It is expected that SELECT has already been performed on the card
|
/// It is expected that SELECT has already been performed on the card
|
||||||
|
@ -168,673 +178,6 @@ pub trait CardTransaction {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- get data ---
|
|
||||||
|
|
||||||
/// Get the "application related data" from the card.
|
|
||||||
///
|
|
||||||
/// (This data should probably be cached in a higher layer. Some parts of
|
|
||||||
/// it are needed regularly, and it does not usually change during
|
|
||||||
/// normal use of a card.)
|
|
||||||
fn application_related_data(&mut self) -> Result<ApplicationRelatedData> {
|
|
||||||
let ad = commands::application_related_data();
|
|
||||||
let resp = apdu::send_command(self, ad, true)?;
|
|
||||||
let value = Value::from(resp.data()?, true)?;
|
|
||||||
|
|
||||||
log::debug!(" ARD value: {:x?}", value);
|
|
||||||
|
|
||||||
Ok(ApplicationRelatedData(Tlv::new(Tag::from([0x6E]), value)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// #[allow(dead_code)]
|
|
||||||
// fn ca_fingerprints() {
|
|
||||||
// unimplemented!()
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// #[allow(dead_code)]
|
|
||||||
// fn key_information() {
|
|
||||||
// unimplemented!()
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// #[allow(dead_code)]
|
|
||||||
// fn uif_pso_cds() {
|
|
||||||
// unimplemented!()
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// #[allow(dead_code)]
|
|
||||||
// fn uif_pso_dec() {
|
|
||||||
// unimplemented!()
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// #[allow(dead_code)]
|
|
||||||
// fn uif_pso_aut() {
|
|
||||||
// unimplemented!()
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// #[allow(dead_code)]
|
|
||||||
// fn uif_attestation() {
|
|
||||||
// unimplemented!()
|
|
||||||
// }
|
|
||||||
|
|
||||||
// --- login data (5e) ---
|
|
||||||
|
|
||||||
/// Get URL (5f50)
|
|
||||||
fn url(&mut self) -> Result<Vec<u8>> {
|
|
||||||
let resp = apdu::send_command(self, commands::url(), true)?;
|
|
||||||
|
|
||||||
Ok(resp.data()?.to_vec())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get cardholder related data (65)
|
|
||||||
fn cardholder_related_data(&mut self) -> Result<CardholderRelatedData> {
|
|
||||||
let crd = commands::cardholder_related_data();
|
|
||||||
let resp = apdu::send_command(self, crd, true)?;
|
|
||||||
resp.check_ok()?;
|
|
||||||
|
|
||||||
CardholderRelatedData::try_from(resp.data()?)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get security support template (7a)
|
|
||||||
fn security_support_template(&mut self) -> Result<SecuritySupportTemplate> {
|
|
||||||
let sst = commands::security_support_template();
|
|
||||||
let resp = apdu::send_command(self, sst, true)?;
|
|
||||||
resp.check_ok()?;
|
|
||||||
|
|
||||||
let tlv = Tlv::try_from(resp.data()?)?;
|
|
||||||
let res = tlv
|
|
||||||
.find(&[0x93].into())
|
|
||||||
.ok_or_else(|| anyhow!("Couldn't get SecuritySupportTemplate DO"))?;
|
|
||||||
|
|
||||||
if let Value::S(data) = res {
|
|
||||||
let mut data = data.to_vec();
|
|
||||||
assert_eq!(data.len(), 3);
|
|
||||||
|
|
||||||
data.insert(0, 0); // prepend a zero
|
|
||||||
let data: [u8; 4] = data.try_into().unwrap();
|
|
||||||
|
|
||||||
let dsc: u32 = u32::from_be_bytes(data);
|
|
||||||
Ok(SecuritySupportTemplate { dsc })
|
|
||||||
} else {
|
|
||||||
Err(anyhow!("Failed to process SecuritySupportTemplate"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get cardholder certificate (each for AUT, DEC and SIG).
|
|
||||||
///
|
|
||||||
/// Call select_data() before calling this fn, to select a particular
|
|
||||||
/// certificate (if the card supports multiple certificates).
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn cardholder_certificate(&mut self) -> Result<Vec<u8>, Error> {
|
|
||||||
let cmd = commands::cardholder_certificate();
|
|
||||||
apdu::send_command(self, cmd, true)?.try_into()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get "Algorithm Information"
|
|
||||||
fn algorithm_information(&mut self) -> Result<Option<AlgoInfo>> {
|
|
||||||
let resp = apdu::send_command(self, commands::algo_info(), true)?;
|
|
||||||
resp.check_ok()?;
|
|
||||||
|
|
||||||
let ai = AlgoInfo::try_from(resp.data()?)?;
|
|
||||||
Ok(Some(ai))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Firmware Version (YubiKey specific (?))
|
|
||||||
fn firmware_version(&mut self) -> Result<Vec<u8>> {
|
|
||||||
let resp = apdu::send_command(self, commands::firmware_version(), true)?;
|
|
||||||
|
|
||||||
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/>]
|
|
||||||
fn set_identity(&mut self, id: u8) -> Result<Vec<u8>> {
|
|
||||||
let resp = apdu::send_command(self, 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)
|
|
||||||
fn select_data(&mut self, num: u8, tag: &[u8]) -> Result<Vec<u8>, Error> {
|
|
||||||
let tlv = Tlv::new(
|
|
||||||
[0x60],
|
|
||||||
Value::C(vec![Tlv::new([0x5c], Value::S(tag.to_vec()))]),
|
|
||||||
);
|
|
||||||
|
|
||||||
let data = tlv.serialize();
|
|
||||||
|
|
||||||
let cmd = commands::select_data(num, data);
|
|
||||||
apdu::send_command(self, cmd, true)?.try_into()
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- optional private DOs (0101 - 0104) ---
|
|
||||||
|
|
||||||
/// Get data from "private use" DO.
|
|
||||||
///
|
|
||||||
/// `num` must be between 1 and 4.
|
|
||||||
fn private_use_do(&mut self, num: u8) -> Result<Vec<u8>> {
|
|
||||||
assert!((1..=4).contains(&num));
|
|
||||||
|
|
||||||
let cmd = commands::private_use_do(num);
|
|
||||||
let resp = apdu::send_command(self, cmd, true)?;
|
|
||||||
|
|
||||||
Ok(resp.data()?.to_vec())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set data of "private use" DO.
|
|
||||||
///
|
|
||||||
/// `num` must be between 1 and 4.
|
|
||||||
///
|
|
||||||
/// Access condition:
|
|
||||||
/// - 1/3 need PW1 (82)
|
|
||||||
/// - 2/4 need PW3
|
|
||||||
fn set_private_use_do(&mut self, num: u8, data: Vec<u8>) -> Result<Vec<u8>> {
|
|
||||||
assert!((1..=4).contains(&num));
|
|
||||||
|
|
||||||
let cmd = commands::put_private_use_do(num, data);
|
|
||||||
let resp = apdu::send_command(self, cmd, true)?;
|
|
||||||
|
|
||||||
Ok(resp.data()?.to_vec())
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------
|
|
||||||
|
|
||||||
/// Reset all state on this OpenPGP card.
|
|
||||||
///
|
|
||||||
/// Note: the "factory reset" operation is not directly offered by the
|
|
||||||
/// card spec. It is implemented as a series of OpenPGP card commands:
|
|
||||||
/// - send 4 bad requests to verify pw1,
|
|
||||||
/// - send 4 bad requests to verify pw3,
|
|
||||||
/// - terminate_df,
|
|
||||||
/// - activate_file.
|
|
||||||
///
|
|
||||||
/// With most cards, this sequence of operations causes the card
|
|
||||||
/// to revert to a "blank" state.
|
|
||||||
///
|
|
||||||
/// (However, e.g. vanilla Gnuk doesn't support this functionality.
|
|
||||||
/// Gnuk needs to be built with the `--enable-factory-reset`
|
|
||||||
/// option to the `configure` script to enable this functionality).
|
|
||||||
fn factory_reset(&mut 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, verify, false)?;
|
|
||||||
if !(resp.status() == StatusBytes::SecurityStatusNotSatisfied
|
|
||||||
|| resp.status() == StatusBytes::AuthenticationMethodBlocked
|
|
||||||
|| matches!(resp.status(), StatusBytes::PasswordNotChecked(_)))
|
|
||||||
{
|
|
||||||
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, verify, false)?;
|
|
||||||
|
|
||||||
if !(resp.status() == StatusBytes::SecurityStatusNotSatisfied
|
|
||||||
|| resp.status() == StatusBytes::AuthenticationMethodBlocked
|
|
||||||
|| matches!(resp.status(), StatusBytes::PasswordNotChecked(_)))
|
|
||||||
{
|
|
||||||
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, term, false)?;
|
|
||||||
resp.check_ok()?;
|
|
||||||
|
|
||||||
// activate_file [apdu 00 44 00 00]
|
|
||||||
let act = commands::activate_file();
|
|
||||||
let resp = apdu::send_command(self, act, false)?;
|
|
||||||
resp.check_ok()?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- verify/modify ---
|
|
||||||
|
|
||||||
/// Verify pw1 (user) for signing operation (mode 81).
|
|
||||||
///
|
|
||||||
/// Depending on the PW1 status byte (see Extended Capabilities) this
|
|
||||||
/// access condition is only valid for one PSO:CDS command or remains
|
|
||||||
/// valid for several attempts.
|
|
||||||
fn verify_pw1_for_signing(&mut self, pin: &str) -> Result<(), Error> {
|
|
||||||
let verify = commands::verify_pw1_81(pin.as_bytes().to_vec());
|
|
||||||
apdu::send_command(self, verify, false)?.try_into()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Verify pw1 (user) for signing operation (mode 81) using a
|
|
||||||
/// pinpad on the card reader. If no usable pinpad is found, an error
|
|
||||||
/// is returned.
|
|
||||||
///
|
|
||||||
/// Depending on the PW1 status byte (see Extended Capabilities) this
|
|
||||||
/// access condition is only valid for one PSO:CDS command or remains
|
|
||||||
/// valid for several attempts.
|
|
||||||
fn verify_pw1_for_signing_pinpad(&mut self) -> Result<(), Error> {
|
|
||||||
let res = self.pinpad_verify(0x81)?;
|
|
||||||
RawResponse::try_from(res)?.try_into()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check the current access of PW1 for signing (mode 81).
|
|
||||||
///
|
|
||||||
/// If verification is not required, an empty Ok Response is returned.
|
|
||||||
///
|
|
||||||
/// (Note:
|
|
||||||
/// - some cards don't correctly implement this feature, e.g. YubiKey 5
|
|
||||||
/// - some cards that don't support this instruction may decrease the pin's error count,
|
|
||||||
/// eventually requiring the user to reset the pin)
|
|
||||||
fn check_pw1_for_signing(&mut self) -> Result<(), Error> {
|
|
||||||
let verify = commands::verify_pw1_81(vec![]);
|
|
||||||
apdu::send_command(self, verify, false)?.try_into()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Verify PW1 (user).
|
|
||||||
/// (For operations except signing, mode 82).
|
|
||||||
fn verify_pw1(&mut self, pin: &str) -> Result<(), Error> {
|
|
||||||
let verify = commands::verify_pw1_82(pin.as_bytes().to_vec());
|
|
||||||
apdu::send_command(self, verify, false)?.try_into()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Verify PW1 (user) for operations except signing (mode 82),
|
|
||||||
/// using a pinpad on the card reader. If no usable pinpad is found,
|
|
||||||
/// an error is returned.
|
|
||||||
|
|
||||||
fn verify_pw1_pinpad(&mut self) -> Result<(), Error> {
|
|
||||||
let res = self.pinpad_verify(0x82)?;
|
|
||||||
RawResponse::try_from(res)?.try_into()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check the current access of PW1.
|
|
||||||
/// (For operations except signing, mode 82).
|
|
||||||
///
|
|
||||||
/// If verification is not required, an empty Ok Response is returned.
|
|
||||||
///
|
|
||||||
/// (Note:
|
|
||||||
/// - some cards don't correctly implement this feature, e.g. YubiKey 5
|
|
||||||
/// - some cards that don't support this instruction may decrease the pin's error count,
|
|
||||||
/// eventually requiring the user to reset the pin)
|
|
||||||
fn check_pw1(&mut self) -> Result<(), Error> {
|
|
||||||
let verify = commands::verify_pw1_82(vec![]);
|
|
||||||
apdu::send_command(self, verify, false)?.try_into()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Verify PW3 (admin).
|
|
||||||
fn verify_pw3(&mut self, pin: &str) -> Result<(), Error> {
|
|
||||||
let verify = commands::verify_pw3(pin.as_bytes().to_vec());
|
|
||||||
apdu::send_command(self, verify, false)?.try_into()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Verify PW3 (admin) using a pinpad on the card reader. If no usable
|
|
||||||
/// pinpad is found, an error is returned.
|
|
||||||
fn verify_pw3_pinpad(&mut self) -> Result<(), Error> {
|
|
||||||
let res = self.pinpad_verify(0x83)?;
|
|
||||||
RawResponse::try_from(res)?.try_into()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check the current access of PW3 (admin).
|
|
||||||
///
|
|
||||||
/// If verification is not required, an empty Ok Response is returned.
|
|
||||||
///
|
|
||||||
/// (Note:
|
|
||||||
/// - some cards don't correctly implement this feature, e.g. YubiKey 5
|
|
||||||
/// - some cards that don't support this instruction may decrease the pin's error count,
|
|
||||||
/// eventually requiring the user to reset the pin)
|
|
||||||
fn check_pw3(&mut self) -> Result<(), Error> {
|
|
||||||
let verify = commands::verify_pw3(vec![]);
|
|
||||||
apdu::send_command(self, verify, false)?.try_into()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Change the value of PW1 (user password).
|
|
||||||
///
|
|
||||||
/// The current value of PW1 must be presented in `old` for authorization.
|
|
||||||
fn change_pw1(&mut self, old: &str, new: &str) -> Result<(), Error> {
|
|
||||||
let mut data = vec![];
|
|
||||||
data.extend(old.as_bytes());
|
|
||||||
data.extend(new.as_bytes());
|
|
||||||
|
|
||||||
let change = commands::change_pw1(data);
|
|
||||||
apdu::send_command(self, change, false)?.try_into()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Change the value of PW1 (user password) using a pinpad on the
|
|
||||||
/// card reader. If no usable pinpad is found, an error is returned.
|
|
||||||
fn change_pw1_pinpad(&mut self) -> Result<(), Error> {
|
|
||||||
let res = self.pinpad_modify(0x81)?;
|
|
||||||
RawResponse::try_from(res)?.try_into()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Change the value of PW3 (admin password).
|
|
||||||
///
|
|
||||||
/// The current value of PW3 must be presented in `old` for authorization.
|
|
||||||
fn change_pw3(&mut self, old: &str, new: &str) -> Result<(), Error> {
|
|
||||||
let mut data = vec![];
|
|
||||||
data.extend(old.as_bytes());
|
|
||||||
data.extend(new.as_bytes());
|
|
||||||
|
|
||||||
let change = commands::change_pw3(data);
|
|
||||||
apdu::send_command(self, change, false)?.try_into()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Change the value of PW3 (admin password) using a pinpad on the
|
|
||||||
/// card reader. If no usable pinpad is found, an error is returned.
|
|
||||||
fn change_pw3_pinpad(&mut self) -> Result<(), Error> {
|
|
||||||
let res = self.pinpad_modify(0x83)?;
|
|
||||||
RawResponse::try_from(res)?.try_into()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reset the error counter for PW1 (user password) and set a new value
|
|
||||||
/// for PW1.
|
|
||||||
///
|
|
||||||
/// For authorization, either:
|
|
||||||
/// - PW3 must have been verified previously,
|
|
||||||
/// - secure messaging must be currently used,
|
|
||||||
/// - the resetting_code must be presented.
|
|
||||||
fn reset_retry_counter_pw1(
|
|
||||||
&mut self,
|
|
||||||
new_pw1: Vec<u8>,
|
|
||||||
resetting_code: Option<Vec<u8>>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let reset = commands::reset_retry_counter_pw1(resetting_code, new_pw1);
|
|
||||||
apdu::send_command(self, reset, false)?.try_into()
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- decrypt ---
|
|
||||||
|
|
||||||
/// Decrypt the ciphertext in `dm`, on the card.
|
|
||||||
///
|
|
||||||
/// (This is a wrapper around the low-level pso_decipher
|
|
||||||
/// operation, it builds the required `data` field from `dm`)
|
|
||||||
fn decipher(&mut self, dm: Cryptogram) -> Result<Vec<u8>, Error> {
|
|
||||||
match dm {
|
|
||||||
Cryptogram::RSA(message) => {
|
|
||||||
// "Padding indicator byte (00) for RSA" (pg. 69)
|
|
||||||
let mut data = vec![0x0];
|
|
||||||
data.extend_from_slice(message);
|
|
||||||
|
|
||||||
// Call the card to decrypt `data`
|
|
||||||
self.pso_decipher(data)
|
|
||||||
}
|
|
||||||
Cryptogram::ECDH(eph) => {
|
|
||||||
// "In case of ECDH the card supports a partial decrypt
|
|
||||||
// only. The input is a cipher DO with the following data:"
|
|
||||||
// A6 xx Cipher DO
|
|
||||||
// -> 7F49 xx Public Key DO
|
|
||||||
// -> 86 xx External Public Key
|
|
||||||
|
|
||||||
// External Public Key
|
|
||||||
let epk = Tlv::new([0x86], Value::S(eph.to_vec()));
|
|
||||||
|
|
||||||
// Public Key DO
|
|
||||||
let pkdo = Tlv::new([0x7f, 0x49], Value::C(vec![epk]));
|
|
||||||
|
|
||||||
// Cipher DO
|
|
||||||
let cdo = Tlv::new([0xa6], Value::C(vec![pkdo]));
|
|
||||||
|
|
||||||
self.pso_decipher(cdo.serialize())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Run decryption operation on the smartcard (low level operation)
|
|
||||||
/// (7.2.11 PSO: DECIPHER)
|
|
||||||
///
|
|
||||||
/// (consider using the `decipher()` method if you don't want to create
|
|
||||||
/// the data field manually)
|
|
||||||
fn pso_decipher(&mut self, data: Vec<u8>) -> Result<Vec<u8>, Error> {
|
|
||||||
// The OpenPGP card is already connected and PW1 82 has been verified
|
|
||||||
let dec_cmd = commands::decryption(data);
|
|
||||||
let resp = apdu::send_command(self, dec_cmd, true)?;
|
|
||||||
resp.check_ok()?;
|
|
||||||
|
|
||||||
Ok(resp.data().map(|d| d.to_vec())?)
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- sign ---
|
|
||||||
|
|
||||||
/// Sign `hash`, on the card.
|
|
||||||
///
|
|
||||||
/// This is a wrapper around the low-level
|
|
||||||
/// pso_compute_digital_signature operation.
|
|
||||||
/// It builds the required `data` field from `hash`.
|
|
||||||
///
|
|
||||||
/// For RSA, this means a "DigestInfo" data structure is generated.
|
|
||||||
/// (see 7.2.10.2 DigestInfo for RSA).
|
|
||||||
///
|
|
||||||
/// With ECC the hash data is processed as is, using
|
|
||||||
/// pso_compute_digital_signature.
|
|
||||||
fn signature_for_hash(&mut self, hash: Hash) -> Result<Vec<u8>, Error> {
|
|
||||||
self.pso_compute_digital_signature(digestinfo(hash))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Run signing operation on the smartcard (low level operation)
|
|
||||||
/// (7.2.10 PSO: COMPUTE DIGITAL SIGNATURE)
|
|
||||||
///
|
|
||||||
/// (consider using the `signature_for_hash()` method if you don't
|
|
||||||
/// want to create the data field manually)
|
|
||||||
fn pso_compute_digital_signature(&mut self, data: Vec<u8>) -> Result<Vec<u8>, Error> {
|
|
||||||
let cds_cmd = commands::signature(data);
|
|
||||||
|
|
||||||
let resp = apdu::send_command(self, cds_cmd, true)?;
|
|
||||||
|
|
||||||
Ok(resp.data().map(|d| d.to_vec())?)
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- internal authenticate ---
|
|
||||||
|
|
||||||
/// Auth-sign `hash`, on the card.
|
|
||||||
///
|
|
||||||
/// This is a wrapper around the low-level
|
|
||||||
/// internal_authenticate operation.
|
|
||||||
/// It builds the required `data` field from `hash`.
|
|
||||||
///
|
|
||||||
/// For RSA, this means a "DigestInfo" data structure is generated.
|
|
||||||
/// (see 7.2.10.2 DigestInfo for RSA).
|
|
||||||
///
|
|
||||||
/// With ECC the hash data is processed as is.
|
|
||||||
fn authenticate_for_hash(&mut self, hash: Hash) -> Result<Vec<u8>, Error> {
|
|
||||||
self.internal_authenticate(digestinfo(hash))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Run signing operation on the smartcard (low level operation)
|
|
||||||
/// (7.2.13 INTERNAL AUTHENTICATE)
|
|
||||||
///
|
|
||||||
/// (consider using the `authenticate_for_hash()` method if you don't
|
|
||||||
/// want to create the data field manually)
|
|
||||||
fn internal_authenticate(&mut self, data: Vec<u8>) -> Result<Vec<u8>, Error> {
|
|
||||||
let ia_cmd = commands::internal_authenticate(data);
|
|
||||||
let resp = apdu::send_command(self, ia_cmd, true)?;
|
|
||||||
|
|
||||||
Ok(resp.data().map(|d| d.to_vec())?)
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- admin ---
|
|
||||||
|
|
||||||
fn set_name(&mut self, name: &[u8]) -> Result<(), Error> {
|
|
||||||
let put_name = commands::put_name(name.to_vec());
|
|
||||||
apdu::send_command(self, put_name, false)?.try_into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_lang(&mut self, lang: &[Lang]) -> Result<(), Error> {
|
|
||||||
let bytes: Vec<u8> = lang
|
|
||||||
.iter()
|
|
||||||
.map(|&l| Into::<Vec<u8>>::into(l))
|
|
||||||
.flatten()
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let put_lang = commands::put_lang(bytes);
|
|
||||||
apdu::send_command(self, put_lang, false)?.try_into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_sex(&mut self, sex: Sex) -> Result<(), Error> {
|
|
||||||
let put_sex = commands::put_sex((&sex).into());
|
|
||||||
apdu::send_command(self, put_sex, false)?.try_into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_url(&mut self, url: &[u8]) -> Result<(), Error> {
|
|
||||||
let put_url = commands::put_url(url.to_vec());
|
|
||||||
apdu::send_command(self, put_url, false)?.try_into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_creation_time(
|
|
||||||
&mut self,
|
|
||||||
time: KeyGenerationTime,
|
|
||||||
key_type: KeyType,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
// Timestamp update
|
|
||||||
let time_value: Vec<u8> = time
|
|
||||||
.get()
|
|
||||||
.to_be_bytes()
|
|
||||||
.iter()
|
|
||||||
.skip_while(|&&e| e == 0)
|
|
||||||
.copied()
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let time_cmd = commands::put_data(&[key_type.timestamp_put_tag()], time_value);
|
|
||||||
|
|
||||||
apdu::send_command(self, time_cmd, false)?.try_into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_fingerprint(&mut self, fp: Fingerprint, key_type: KeyType) -> Result<(), Error> {
|
|
||||||
let fp_cmd = commands::put_data(&[key_type.fingerprint_put_tag()], fp.as_bytes().to_vec());
|
|
||||||
|
|
||||||
apdu::send_command(self, fp_cmd, false)?.try_into()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set PW Status Bytes.
|
|
||||||
///
|
|
||||||
/// If `long` is false, send 1 byte to the card, otherwise 4.
|
|
||||||
/// According to the spec, length information should not be changed.
|
|
||||||
///
|
|
||||||
/// So, effectively, with 'long == false' the setting `pw1_cds_multi`
|
|
||||||
/// can be changed.
|
|
||||||
/// With 'long == true', the settings `pw1_pin_block` and `pw3_pin_block`
|
|
||||||
/// can also be changed.
|
|
||||||
///
|
|
||||||
/// (See OpenPGP card spec, pg. 28)
|
|
||||||
fn set_pw_status_bytes(&mut self, pw_status: &PWStatusBytes, long: bool) -> Result<(), Error> {
|
|
||||||
let data = pw_status.serialize_for_put(long);
|
|
||||||
|
|
||||||
let cmd = commands::put_pw_status(data);
|
|
||||||
apdu::send_command(self, cmd, false)?.try_into()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set cardholder certificate (for AUT, DEC or SIG).
|
|
||||||
///
|
|
||||||
/// Call select_data() before calling this fn, to select a particular
|
|
||||||
/// certificate (if the card supports multiple certificates).
|
|
||||||
fn set_cardholder_certificate(&mut self, data: Vec<u8>) -> Result<(), Error> {
|
|
||||||
let cmd = commands::put_cardholder_certificate(data);
|
|
||||||
apdu::send_command(self, cmd, false)?.try_into()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set algorithm attributes
|
|
||||||
/// (4.4.3.9 Algorithm Attributes)
|
|
||||||
fn set_algorithm_attributes(&mut self, key_type: KeyType, algo: &Algo) -> Result<(), Error> {
|
|
||||||
// Command to PUT the algorithm attributes
|
|
||||||
let cmd = commands::put_data(&[key_type.algorithm_tag()], algo.to_data_object()?);
|
|
||||||
|
|
||||||
apdu::send_command(self, cmd, false)?.try_into()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set resetting code
|
|
||||||
/// (4.3.4 Resetting Code)
|
|
||||||
fn set_resetting_code(&mut self, resetting_code: Vec<u8>) -> Result<(), Error> {
|
|
||||||
let cmd = commands::put_data(&[0xd3], resetting_code);
|
|
||||||
apdu::send_command(self, cmd, false)?.try_into()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Import an existing private key to the card.
|
|
||||||
/// (This implicitly sets the algorithm info, fingerprint and timestamp)
|
|
||||||
fn key_import(
|
|
||||||
&mut self,
|
|
||||||
key: Box<dyn CardUploadableKey>,
|
|
||||||
key_type: KeyType,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let algo_info = self.algorithm_information();
|
|
||||||
|
|
||||||
// An error is ok - it's fine if a card doesn't offer a list of
|
|
||||||
// supported algorithms
|
|
||||||
let algo_info = algo_info.unwrap_or(None);
|
|
||||||
|
|
||||||
keys::key_import(self, key, key_type, algo_info)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generate a key on the card.
|
|
||||||
/// (7.2.14 GENERATE ASYMMETRIC KEY PAIR)
|
|
||||||
///
|
|
||||||
/// If the `algo` parameter is Some, then this algorithm will be set on
|
|
||||||
/// the card for "key_type".
|
|
||||||
///
|
|
||||||
/// Note: `algo` needs to precisely specify the RSA bitsize of e (if
|
|
||||||
/// applicable), and import format, with values that the current card
|
|
||||||
/// supports.
|
|
||||||
fn generate_key(
|
|
||||||
&mut self,
|
|
||||||
fp_from_pub: fn(
|
|
||||||
&PublicKeyMaterial,
|
|
||||||
KeyGenerationTime,
|
|
||||||
KeyType,
|
|
||||||
) -> Result<Fingerprint, Error>,
|
|
||||||
key_type: KeyType,
|
|
||||||
algo: Option<&Algo>,
|
|
||||||
) -> Result<(PublicKeyMaterial, KeyGenerationTime), Error> {
|
|
||||||
keys::gen_key_with_metadata(self, fp_from_pub, key_type, algo)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generate a key on the card.
|
|
||||||
/// (7.2.14 GENERATE ASYMMETRIC KEY PAIR)
|
|
||||||
///
|
|
||||||
/// This is a wrapper around generate_key() which allows
|
|
||||||
/// using the simplified `AlgoSimple` algorithm selector enum.
|
|
||||||
///
|
|
||||||
/// Note: AlgoSimple doesn't specify card specific details (such as
|
|
||||||
/// bitsize of e for RSA, and import format). This function determines
|
|
||||||
/// these values based on information from the card.
|
|
||||||
fn generate_key_simple(
|
|
||||||
&mut self,
|
|
||||||
fp_from_pub: fn(
|
|
||||||
&PublicKeyMaterial,
|
|
||||||
KeyGenerationTime,
|
|
||||||
KeyType,
|
|
||||||
) -> Result<Fingerprint, Error>,
|
|
||||||
key_type: KeyType,
|
|
||||||
simple: AlgoSimple,
|
|
||||||
) -> Result<(PublicKeyMaterial, KeyGenerationTime), Error> {
|
|
||||||
let ard = self.application_related_data()?;
|
|
||||||
let algo_info = if let Ok(ai) = self.algorithm_information() {
|
|
||||||
ai
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let algo = simple.determine_algo(key_type, &ard, algo_info)?;
|
|
||||||
|
|
||||||
Self::generate_key(self, fp_from_pub, key_type, Some(&algo))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get public key material from the card.
|
|
||||||
///
|
|
||||||
/// Note: this fn returns a set of raw public key data (not an
|
|
||||||
/// OpenPGP data structure).
|
|
||||||
///
|
|
||||||
/// Note also that the information from the card is insufficient to
|
|
||||||
/// reconstruct a pre-existing OpenPGP public key that corresponds to
|
|
||||||
/// the private key on the card.
|
|
||||||
fn public_key(&mut self, key_type: KeyType) -> Result<PublicKeyMaterial, Error> {
|
|
||||||
keys::public_key(self, key_type)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Deref for dyn CardTransaction + Send + Sync + 'a {
|
impl<'a> Deref for dyn CardTransaction + Send + Sync + 'a {
|
||||||
|
@ -850,40 +193,15 @@ impl<'a> DerefMut for dyn CardTransaction + Send + Sync + 'a {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn digestinfo(hash: Hash) -> Vec<u8> {
|
/// Information about the capabilities of a card.
|
||||||
match hash {
|
|
||||||
Hash::SHA256(_) | Hash::SHA384(_) | Hash::SHA512(_) => {
|
|
||||||
let tlv = Tlv::new(
|
|
||||||
[0x30],
|
|
||||||
Value::C(vec![
|
|
||||||
Tlv::new(
|
|
||||||
[0x30],
|
|
||||||
Value::C(vec![
|
|
||||||
Tlv::new(
|
|
||||||
[0x06],
|
|
||||||
// unwrapping is ok, for SHA*
|
|
||||||
Value::S(hash.oid().unwrap().to_vec()),
|
|
||||||
),
|
|
||||||
Tlv::new([0x05], Value::S(vec![])),
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
Tlv::new([0x04], Value::S(hash.digest().to_vec())),
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
|
|
||||||
tlv.serialize()
|
|
||||||
}
|
|
||||||
Hash::EdDSA(d) => d.to_vec(),
|
|
||||||
Hash::ECDSA(d) => d.to_vec(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Configuration of the capabilities of a card.
|
|
||||||
///
|
///
|
||||||
/// This configuration is used to determine e.g. if chaining or extended
|
/// This configuration can is used to determine e.g. if chaining or extended
|
||||||
/// length can be used when communicating with the card.
|
/// length can be used when communicating with the card.
|
||||||
///
|
///
|
||||||
/// (This configuration is retrieved from card metadata, specifically from
|
/// CardCaps data is used internally and can be used by card backends, it is unlikely to be
|
||||||
|
/// useful for users of this library.
|
||||||
|
///
|
||||||
|
/// (The information is retrieved from card metadata, specifically from
|
||||||
/// "Card Capabilities", "Extended length information" and "PWStatus")
|
/// "Card Capabilities", "Extended length information" and "PWStatus")
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct CardCaps {
|
pub struct CardCaps {
|
||||||
|
|
772
openpgp-card/src/openpgp.rs
Normal file
772
openpgp-card/src/openpgp.rs
Normal file
|
@ -0,0 +1,772 @@
|
||||||
|
// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer <heiko@schaefer.name>
|
||||||
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
|
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use std::convert::{TryFrom, TryInto};
|
||||||
|
|
||||||
|
use crate::algorithm::{Algo, AlgoInfo, AlgoSimple};
|
||||||
|
use crate::apdu::commands;
|
||||||
|
use crate::apdu::response::RawResponse;
|
||||||
|
use crate::card_do::{
|
||||||
|
ApplicationRelatedData, CardholderRelatedData, Fingerprint, KeyGenerationTime, Lang,
|
||||||
|
PWStatusBytes, SecuritySupportTemplate, Sex,
|
||||||
|
};
|
||||||
|
use crate::crypto_data::{CardUploadableKey, Cryptogram, Hash, PublicKeyMaterial};
|
||||||
|
use crate::tlv::{value::Value, Tlv};
|
||||||
|
use crate::{
|
||||||
|
apdu, keys, CardBackend, CardTransaction, Error, KeyType, SmartcardError, StatusBytes,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// An OpenPGP card access object, backed by a CardBackend implementation.
|
||||||
|
///
|
||||||
|
/// Most users will probably want to use the `PcscCard` backend from the `openpgp-card-pcsc` crate.
|
||||||
|
///
|
||||||
|
/// Users of this crate can keep a long lived OpenPgp object. All operations must be performed on
|
||||||
|
/// a short lived `OpenPgpTransaction`.
|
||||||
|
pub struct OpenPgp<'a> {
|
||||||
|
card: &'a mut dyn CardBackend,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> OpenPgp<'a> {
|
||||||
|
pub fn new(card: &'a mut dyn CardBackend) -> Self {
|
||||||
|
Self { card }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get an OpenPgpTransaction object. This starts a transaction on the underlying
|
||||||
|
/// CardBackend.
|
||||||
|
///
|
||||||
|
/// Note: transactions on the Card cannot be long running, they will be reset within seconds
|
||||||
|
/// when idle.
|
||||||
|
pub fn transaction(&mut self) -> Result<OpenPgpTransaction, Error> {
|
||||||
|
Ok(OpenPgpTransaction {
|
||||||
|
tx: self.card.transaction()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Low-level access to OpenPGP card functionality.
|
||||||
|
///
|
||||||
|
/// On backends that support transactions, operations are grouped together in transaction, while
|
||||||
|
/// an object of this type lives.
|
||||||
|
///
|
||||||
|
/// An OpenPgpTransaction on typical underlying card subsystems must be short lived. Typically,
|
||||||
|
/// smart cards can't be kept open for longer than a few seconds, before they are automatically
|
||||||
|
/// closed.
|
||||||
|
pub struct OpenPgpTransaction<'a> {
|
||||||
|
tx: Box<dyn CardTransaction + Send + Sync + 'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> OpenPgpTransaction<'a> {
|
||||||
|
pub(crate) fn tx(&mut self) -> &mut dyn CardTransaction {
|
||||||
|
self.tx.as_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- pinpad ---
|
||||||
|
|
||||||
|
/// Does the reader support FEATURE_VERIFY_PIN_DIRECT?
|
||||||
|
pub fn feature_pinpad_verify(&self) -> bool {
|
||||||
|
self.tx.feature_pinpad_verify()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Does the reader support FEATURE_MODIFY_PIN_DIRECT?
|
||||||
|
pub fn feature_pinpad_modify(&self) -> bool {
|
||||||
|
self.tx.feature_pinpad_modify()
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- get data ---
|
||||||
|
|
||||||
|
/// Get the "application related data" from the card.
|
||||||
|
///
|
||||||
|
/// (This data should probably be cached in a higher layer. Some parts of
|
||||||
|
/// it are needed regularly, and it does not usually change during
|
||||||
|
/// normal use of a card.)
|
||||||
|
pub fn application_related_data(&mut self) -> Result<ApplicationRelatedData> {
|
||||||
|
self.tx.application_related_data()
|
||||||
|
}
|
||||||
|
|
||||||
|
// #[allow(dead_code)]
|
||||||
|
// fn ca_fingerprints() {
|
||||||
|
// unimplemented!()
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// #[allow(dead_code)]
|
||||||
|
// fn key_information() {
|
||||||
|
// unimplemented!()
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// #[allow(dead_code)]
|
||||||
|
// fn uif_pso_cds() {
|
||||||
|
// unimplemented!()
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// #[allow(dead_code)]
|
||||||
|
// fn uif_pso_dec() {
|
||||||
|
// unimplemented!()
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// #[allow(dead_code)]
|
||||||
|
// fn uif_pso_aut() {
|
||||||
|
// unimplemented!()
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// #[allow(dead_code)]
|
||||||
|
// fn uif_attestation() {
|
||||||
|
// unimplemented!()
|
||||||
|
// }
|
||||||
|
|
||||||
|
// --- login data (5e) ---
|
||||||
|
|
||||||
|
/// Get URL (5f50)
|
||||||
|
pub fn url(&mut self) -> Result<Vec<u8>> {
|
||||||
|
let resp = apdu::send_command(self.tx(), commands::url(), true)?;
|
||||||
|
|
||||||
|
Ok(resp.data()?.to_vec())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get cardholder related data (65)
|
||||||
|
pub fn cardholder_related_data(&mut self) -> Result<CardholderRelatedData> {
|
||||||
|
let crd = commands::cardholder_related_data();
|
||||||
|
let resp = apdu::send_command(self.tx(), crd, true)?;
|
||||||
|
resp.check_ok()?;
|
||||||
|
|
||||||
|
CardholderRelatedData::try_from(resp.data()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get security support template (7a)
|
||||||
|
pub fn security_support_template(&mut self) -> Result<SecuritySupportTemplate> {
|
||||||
|
let sst = commands::security_support_template();
|
||||||
|
let resp = apdu::send_command(self.tx(), sst, true)?;
|
||||||
|
resp.check_ok()?;
|
||||||
|
|
||||||
|
let tlv = Tlv::try_from(resp.data()?)?;
|
||||||
|
let res = tlv
|
||||||
|
.find(&[0x93].into())
|
||||||
|
.ok_or_else(|| anyhow!("Couldn't get SecuritySupportTemplate DO"))?;
|
||||||
|
|
||||||
|
if let Value::S(data) = res {
|
||||||
|
let mut data = data.to_vec();
|
||||||
|
assert_eq!(data.len(), 3);
|
||||||
|
|
||||||
|
data.insert(0, 0); // prepend a zero
|
||||||
|
let data: [u8; 4] = data.try_into().unwrap();
|
||||||
|
|
||||||
|
let dsc: u32 = u32::from_be_bytes(data);
|
||||||
|
Ok(SecuritySupportTemplate { dsc })
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("Failed to process SecuritySupportTemplate"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get cardholder certificate (each for AUT, DEC and SIG).
|
||||||
|
///
|
||||||
|
/// Call select_data() before calling this fn, to select a particular
|
||||||
|
/// certificate (if the card supports multiple certificates).
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn cardholder_certificate(&mut self) -> Result<Vec<u8>, Error> {
|
||||||
|
let cmd = commands::cardholder_certificate();
|
||||||
|
apdu::send_command(self.tx(), cmd, true)?.try_into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get "Algorithm Information"
|
||||||
|
pub fn algorithm_information(&mut self) -> Result<Option<AlgoInfo>> {
|
||||||
|
let resp = apdu::send_command(self.tx(), commands::algo_info(), true)?;
|
||||||
|
resp.check_ok()?;
|
||||||
|
|
||||||
|
let ai = AlgoInfo::try_from(resp.data()?)?;
|
||||||
|
Ok(Some(ai))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Firmware Version (YubiKey specific (?))
|
||||||
|
pub fn firmware_version(&mut self) -> Result<Vec<u8>> {
|
||||||
|
let resp = apdu::send_command(self.tx(), commands::firmware_version(), true)?;
|
||||||
|
|
||||||
|
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.tx(), 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(&mut self, num: u8, tag: &[u8]) -> Result<Vec<u8>, Error> {
|
||||||
|
let tlv = Tlv::new(
|
||||||
|
[0x60],
|
||||||
|
Value::C(vec![Tlv::new([0x5c], Value::S(tag.to_vec()))]),
|
||||||
|
);
|
||||||
|
|
||||||
|
let data = tlv.serialize();
|
||||||
|
|
||||||
|
let cmd = commands::select_data(num, data);
|
||||||
|
apdu::send_command(self.tx(), cmd, true)?.try_into()
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- optional private DOs (0101 - 0104) ---
|
||||||
|
|
||||||
|
/// Get data from "private use" DO.
|
||||||
|
///
|
||||||
|
/// `num` must be between 1 and 4.
|
||||||
|
pub fn private_use_do(&mut self, num: u8) -> Result<Vec<u8>> {
|
||||||
|
assert!((1..=4).contains(&num));
|
||||||
|
|
||||||
|
let cmd = commands::private_use_do(num);
|
||||||
|
let resp = apdu::send_command(self.tx(), cmd, true)?;
|
||||||
|
|
||||||
|
Ok(resp.data()?.to_vec())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set data of "private use" DO.
|
||||||
|
///
|
||||||
|
/// `num` must be between 1 and 4.
|
||||||
|
///
|
||||||
|
/// Access condition:
|
||||||
|
/// - 1/3 need PW1 (82)
|
||||||
|
/// - 2/4 need PW3
|
||||||
|
pub fn set_private_use_do(&mut self, num: u8, data: Vec<u8>) -> Result<Vec<u8>> {
|
||||||
|
assert!((1..=4).contains(&num));
|
||||||
|
|
||||||
|
let cmd = commands::put_private_use_do(num, data);
|
||||||
|
let resp = apdu::send_command(self.tx(), cmd, true)?;
|
||||||
|
|
||||||
|
Ok(resp.data()?.to_vec())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------
|
||||||
|
|
||||||
|
/// Reset all state on this OpenPGP card.
|
||||||
|
///
|
||||||
|
/// Note: the "factory reset" operation is not directly offered by the
|
||||||
|
/// card spec. It is implemented as a series of OpenPGP card commands:
|
||||||
|
/// - send 4 bad requests to verify pw1,
|
||||||
|
/// - send 4 bad requests to verify pw3,
|
||||||
|
/// - terminate_df,
|
||||||
|
/// - activate_file.
|
||||||
|
///
|
||||||
|
/// With most cards, this sequence of operations causes the card
|
||||||
|
/// to revert to a "blank" state.
|
||||||
|
///
|
||||||
|
/// (However, e.g. vanilla Gnuk doesn't support this functionality.
|
||||||
|
/// Gnuk needs to be built with the `--enable-factory-reset`
|
||||||
|
/// option to the `configure` script to enable this functionality).
|
||||||
|
pub fn factory_reset(&mut 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.tx(), verify, false)?;
|
||||||
|
if !(resp.status() == StatusBytes::SecurityStatusNotSatisfied
|
||||||
|
|| resp.status() == StatusBytes::AuthenticationMethodBlocked
|
||||||
|
|| matches!(resp.status(), StatusBytes::PasswordNotChecked(_)))
|
||||||
|
{
|
||||||
|
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.tx(), verify, false)?;
|
||||||
|
|
||||||
|
if !(resp.status() == StatusBytes::SecurityStatusNotSatisfied
|
||||||
|
|| resp.status() == StatusBytes::AuthenticationMethodBlocked
|
||||||
|
|| matches!(resp.status(), StatusBytes::PasswordNotChecked(_)))
|
||||||
|
{
|
||||||
|
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.tx(), term, false)?;
|
||||||
|
resp.check_ok()?;
|
||||||
|
|
||||||
|
// activate_file [apdu 00 44 00 00]
|
||||||
|
let act = commands::activate_file();
|
||||||
|
let resp = apdu::send_command(self.tx(), act, false)?;
|
||||||
|
resp.check_ok()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- verify/modify ---
|
||||||
|
|
||||||
|
/// Verify pw1 (user) for signing operation (mode 81).
|
||||||
|
///
|
||||||
|
/// Depending on the PW1 status byte (see Extended Capabilities) this
|
||||||
|
/// access condition is only valid for one PSO:CDS command or remains
|
||||||
|
/// valid for several attempts.
|
||||||
|
pub fn verify_pw1_for_signing(&mut self, pin: &str) -> Result<(), Error> {
|
||||||
|
let verify = commands::verify_pw1_81(pin.as_bytes().to_vec());
|
||||||
|
apdu::send_command(self.tx(), verify, false)?.try_into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verify pw1 (user) for signing operation (mode 81) using a
|
||||||
|
/// pinpad on the card reader. If no usable pinpad is found, an error
|
||||||
|
/// is returned.
|
||||||
|
///
|
||||||
|
/// Depending on the PW1 status byte (see Extended Capabilities) this
|
||||||
|
/// access condition is only valid for one PSO:CDS command or remains
|
||||||
|
/// valid for several attempts.
|
||||||
|
pub fn verify_pw1_for_signing_pinpad(&mut self) -> Result<(), Error> {
|
||||||
|
let res = self.tx().pinpad_verify(0x81)?;
|
||||||
|
RawResponse::try_from(res)?.try_into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check the current access of PW1 for signing (mode 81).
|
||||||
|
///
|
||||||
|
/// If verification is not required, an empty Ok Response is returned.
|
||||||
|
///
|
||||||
|
/// (Note:
|
||||||
|
/// - some cards don't correctly implement this feature, e.g. YubiKey 5
|
||||||
|
/// - some cards that don't support this instruction may decrease the pin's error count,
|
||||||
|
/// eventually requiring the user to reset the pin)
|
||||||
|
pub fn check_pw1_for_signing(&mut self) -> Result<(), Error> {
|
||||||
|
let verify = commands::verify_pw1_81(vec![]);
|
||||||
|
apdu::send_command(self.tx(), verify, false)?.try_into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verify PW1 (user).
|
||||||
|
/// (For operations except signing, mode 82).
|
||||||
|
pub fn verify_pw1(&mut self, pin: &str) -> Result<(), Error> {
|
||||||
|
let verify = commands::verify_pw1_82(pin.as_bytes().to_vec());
|
||||||
|
apdu::send_command(self.tx(), verify, false)?.try_into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verify PW1 (user) for operations except signing (mode 82),
|
||||||
|
/// using a pinpad on the card reader. If no usable pinpad is found,
|
||||||
|
/// an error is returned.
|
||||||
|
|
||||||
|
pub fn verify_pw1_pinpad(&mut self) -> Result<(), Error> {
|
||||||
|
let res = self.tx().pinpad_verify(0x82)?;
|
||||||
|
RawResponse::try_from(res)?.try_into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check the current access of PW1.
|
||||||
|
/// (For operations except signing, mode 82).
|
||||||
|
///
|
||||||
|
/// If verification is not required, an empty Ok Response is returned.
|
||||||
|
///
|
||||||
|
/// (Note:
|
||||||
|
/// - some cards don't correctly implement this feature, e.g. YubiKey 5
|
||||||
|
/// - some cards that don't support this instruction may decrease the pin's error count,
|
||||||
|
/// eventually requiring the user to reset the pin)
|
||||||
|
pub fn check_pw1(&mut self) -> Result<(), Error> {
|
||||||
|
let verify = commands::verify_pw1_82(vec![]);
|
||||||
|
apdu::send_command(self.tx(), verify, false)?.try_into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verify PW3 (admin).
|
||||||
|
pub fn verify_pw3(&mut self, pin: &str) -> Result<(), Error> {
|
||||||
|
let verify = commands::verify_pw3(pin.as_bytes().to_vec());
|
||||||
|
apdu::send_command(self.tx(), verify, false)?.try_into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verify PW3 (admin) using a pinpad on the card reader. If no usable
|
||||||
|
/// pinpad is found, an error is returned.
|
||||||
|
pub fn verify_pw3_pinpad(&mut self) -> Result<(), Error> {
|
||||||
|
let res = self.tx().pinpad_verify(0x83)?;
|
||||||
|
RawResponse::try_from(res)?.try_into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check the current access of PW3 (admin).
|
||||||
|
///
|
||||||
|
/// If verification is not required, an empty Ok Response is returned.
|
||||||
|
///
|
||||||
|
/// (Note:
|
||||||
|
/// - some cards don't correctly implement this feature, e.g. YubiKey 5
|
||||||
|
/// - some cards that don't support this instruction may decrease the pin's error count,
|
||||||
|
/// eventually requiring the user to reset the pin)
|
||||||
|
pub fn check_pw3(&mut self) -> Result<(), Error> {
|
||||||
|
let verify = commands::verify_pw3(vec![]);
|
||||||
|
apdu::send_command(self.tx(), verify, false)?.try_into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Change the value of PW1 (user password).
|
||||||
|
///
|
||||||
|
/// The current value of PW1 must be presented in `old` for authorization.
|
||||||
|
pub fn change_pw1(&mut self, old: &str, new: &str) -> Result<(), Error> {
|
||||||
|
let mut data = vec![];
|
||||||
|
data.extend(old.as_bytes());
|
||||||
|
data.extend(new.as_bytes());
|
||||||
|
|
||||||
|
let change = commands::change_pw1(data);
|
||||||
|
apdu::send_command(self.tx(), change, false)?.try_into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Change the value of PW1 (user password) using a pinpad on the
|
||||||
|
/// card reader. If no usable pinpad is found, an error is returned.
|
||||||
|
pub fn change_pw1_pinpad(&mut self) -> Result<(), Error> {
|
||||||
|
let res = self.tx().pinpad_modify(0x81)?;
|
||||||
|
RawResponse::try_from(res)?.try_into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Change the value of PW3 (admin password).
|
||||||
|
///
|
||||||
|
/// The current value of PW3 must be presented in `old` for authorization.
|
||||||
|
pub fn change_pw3(&mut self, old: &str, new: &str) -> Result<(), Error> {
|
||||||
|
let mut data = vec![];
|
||||||
|
data.extend(old.as_bytes());
|
||||||
|
data.extend(new.as_bytes());
|
||||||
|
|
||||||
|
let change = commands::change_pw3(data);
|
||||||
|
apdu::send_command(self.tx(), change, false)?.try_into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Change the value of PW3 (admin password) using a pinpad on the
|
||||||
|
/// card reader. If no usable pinpad is found, an error is returned.
|
||||||
|
pub fn change_pw3_pinpad(&mut self) -> Result<(), Error> {
|
||||||
|
let res = self.tx().pinpad_modify(0x83)?;
|
||||||
|
RawResponse::try_from(res)?.try_into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reset the error counter for PW1 (user password) and set a new value
|
||||||
|
/// for PW1.
|
||||||
|
///
|
||||||
|
/// For authorization, either:
|
||||||
|
/// - PW3 must have been verified previously,
|
||||||
|
/// - secure messaging must be currently used,
|
||||||
|
/// - the resetting_code must be presented.
|
||||||
|
pub fn reset_retry_counter_pw1(
|
||||||
|
&mut self,
|
||||||
|
new_pw1: Vec<u8>,
|
||||||
|
resetting_code: Option<Vec<u8>>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let reset = commands::reset_retry_counter_pw1(resetting_code, new_pw1);
|
||||||
|
apdu::send_command(self.tx(), reset, false)?.try_into()
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- decrypt ---
|
||||||
|
|
||||||
|
/// Decrypt the ciphertext in `dm`, on the card.
|
||||||
|
///
|
||||||
|
/// (This is a wrapper around the low-level pso_decipher
|
||||||
|
/// operation, it builds the required `data` field from `dm`)
|
||||||
|
pub fn decipher(&mut self, dm: Cryptogram) -> Result<Vec<u8>, Error> {
|
||||||
|
match dm {
|
||||||
|
Cryptogram::RSA(message) => {
|
||||||
|
// "Padding indicator byte (00) for RSA" (pg. 69)
|
||||||
|
let mut data = vec![0x0];
|
||||||
|
data.extend_from_slice(message);
|
||||||
|
|
||||||
|
// Call the card to decrypt `data`
|
||||||
|
self.pso_decipher(data)
|
||||||
|
}
|
||||||
|
Cryptogram::ECDH(eph) => {
|
||||||
|
// "In case of ECDH the card supports a partial decrypt
|
||||||
|
// only. The input is a cipher DO with the following data:"
|
||||||
|
// A6 xx Cipher DO
|
||||||
|
// -> 7F49 xx Public Key DO
|
||||||
|
// -> 86 xx External Public Key
|
||||||
|
|
||||||
|
// External Public Key
|
||||||
|
let epk = Tlv::new([0x86], Value::S(eph.to_vec()));
|
||||||
|
|
||||||
|
// Public Key DO
|
||||||
|
let pkdo = Tlv::new([0x7f, 0x49], Value::C(vec![epk]));
|
||||||
|
|
||||||
|
// Cipher DO
|
||||||
|
let cdo = Tlv::new([0xa6], Value::C(vec![pkdo]));
|
||||||
|
|
||||||
|
self.pso_decipher(cdo.serialize())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run decryption operation on the smartcard (low level operation)
|
||||||
|
/// (7.2.11 PSO: DECIPHER)
|
||||||
|
///
|
||||||
|
/// (consider using the `decipher()` method if you don't want to create
|
||||||
|
/// the data field manually)
|
||||||
|
pub fn pso_decipher(&mut self, data: Vec<u8>) -> Result<Vec<u8>, Error> {
|
||||||
|
// The OpenPGP card is already connected and PW1 82 has been verified
|
||||||
|
let dec_cmd = commands::decryption(data);
|
||||||
|
let resp = apdu::send_command(self.tx(), dec_cmd, true)?;
|
||||||
|
resp.check_ok()?;
|
||||||
|
|
||||||
|
Ok(resp.data().map(|d| d.to_vec())?)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- sign ---
|
||||||
|
|
||||||
|
/// Sign `hash`, on the card.
|
||||||
|
///
|
||||||
|
/// This is a wrapper around the low-level
|
||||||
|
/// pso_compute_digital_signature operation.
|
||||||
|
/// It builds the required `data` field from `hash`.
|
||||||
|
///
|
||||||
|
/// For RSA, this means a "DigestInfo" data structure is generated.
|
||||||
|
/// (see 7.2.10.2 DigestInfo for RSA).
|
||||||
|
///
|
||||||
|
/// With ECC the hash data is processed as is, using
|
||||||
|
/// pso_compute_digital_signature.
|
||||||
|
pub fn signature_for_hash(&mut self, hash: Hash) -> Result<Vec<u8>, Error> {
|
||||||
|
self.pso_compute_digital_signature(digestinfo(hash))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run signing operation on the smartcard (low level operation)
|
||||||
|
/// (7.2.10 PSO: COMPUTE DIGITAL SIGNATURE)
|
||||||
|
///
|
||||||
|
/// (consider using the `signature_for_hash()` method if you don't
|
||||||
|
/// want to create the data field manually)
|
||||||
|
pub fn pso_compute_digital_signature(&mut self, data: Vec<u8>) -> Result<Vec<u8>, Error> {
|
||||||
|
let cds_cmd = commands::signature(data);
|
||||||
|
|
||||||
|
let resp = apdu::send_command(self.tx(), cds_cmd, true)?;
|
||||||
|
|
||||||
|
Ok(resp.data().map(|d| d.to_vec())?)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- internal authenticate ---
|
||||||
|
|
||||||
|
/// Auth-sign `hash`, on the card.
|
||||||
|
///
|
||||||
|
/// This is a wrapper around the low-level
|
||||||
|
/// internal_authenticate operation.
|
||||||
|
/// It builds the required `data` field from `hash`.
|
||||||
|
///
|
||||||
|
/// For RSA, this means a "DigestInfo" data structure is generated.
|
||||||
|
/// (see 7.2.10.2 DigestInfo for RSA).
|
||||||
|
///
|
||||||
|
/// With ECC the hash data is processed as is.
|
||||||
|
pub fn authenticate_for_hash(&mut self, hash: Hash) -> Result<Vec<u8>, Error> {
|
||||||
|
self.internal_authenticate(digestinfo(hash))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run signing operation on the smartcard (low level operation)
|
||||||
|
/// (7.2.13 INTERNAL AUTHENTICATE)
|
||||||
|
///
|
||||||
|
/// (consider using the `authenticate_for_hash()` method if you don't
|
||||||
|
/// want to create the data field manually)
|
||||||
|
pub fn internal_authenticate(&mut self, data: Vec<u8>) -> Result<Vec<u8>, Error> {
|
||||||
|
let ia_cmd = commands::internal_authenticate(data);
|
||||||
|
let resp = apdu::send_command(self.tx(), ia_cmd, true)?;
|
||||||
|
|
||||||
|
Ok(resp.data().map(|d| d.to_vec())?)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- admin ---
|
||||||
|
|
||||||
|
pub fn set_name(&mut self, name: &[u8]) -> Result<(), Error> {
|
||||||
|
let put_name = commands::put_name(name.to_vec());
|
||||||
|
apdu::send_command(self.tx(), put_name, false)?.try_into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_lang(&mut self, lang: &[Lang]) -> Result<(), Error> {
|
||||||
|
let bytes: Vec<u8> = lang
|
||||||
|
.iter()
|
||||||
|
.map(|&l| Into::<Vec<u8>>::into(l))
|
||||||
|
.flatten()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let put_lang = commands::put_lang(bytes);
|
||||||
|
apdu::send_command(self.tx(), put_lang, false)?.try_into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_sex(&mut self, sex: Sex) -> Result<(), Error> {
|
||||||
|
let put_sex = commands::put_sex((&sex).into());
|
||||||
|
apdu::send_command(self.tx(), put_sex, false)?.try_into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_url(&mut self, url: &[u8]) -> Result<(), Error> {
|
||||||
|
let put_url = commands::put_url(url.to_vec());
|
||||||
|
apdu::send_command(self.tx(), put_url, false)?.try_into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_creation_time(
|
||||||
|
&mut self,
|
||||||
|
time: KeyGenerationTime,
|
||||||
|
key_type: KeyType,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
// Timestamp update
|
||||||
|
let time_value: Vec<u8> = time
|
||||||
|
.get()
|
||||||
|
.to_be_bytes()
|
||||||
|
.iter()
|
||||||
|
.skip_while(|&&e| e == 0)
|
||||||
|
.copied()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let time_cmd = commands::put_data(&[key_type.timestamp_put_tag()], time_value);
|
||||||
|
|
||||||
|
apdu::send_command(self.tx(), time_cmd, false)?.try_into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_fingerprint(&mut self, fp: Fingerprint, key_type: KeyType) -> Result<(), Error> {
|
||||||
|
let fp_cmd = commands::put_data(&[key_type.fingerprint_put_tag()], fp.as_bytes().to_vec());
|
||||||
|
|
||||||
|
apdu::send_command(self.tx(), fp_cmd, false)?.try_into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set PW Status Bytes.
|
||||||
|
///
|
||||||
|
/// If `long` is false, send 1 byte to the card, otherwise 4.
|
||||||
|
/// According to the spec, length information should not be changed.
|
||||||
|
///
|
||||||
|
/// So, effectively, with 'long == false' the setting `pw1_cds_multi`
|
||||||
|
/// can be changed.
|
||||||
|
/// With 'long == true', the settings `pw1_pin_block` and `pw3_pin_block`
|
||||||
|
/// can also be changed.
|
||||||
|
///
|
||||||
|
/// (See OpenPGP card spec, pg. 28)
|
||||||
|
pub fn set_pw_status_bytes(
|
||||||
|
&mut self,
|
||||||
|
pw_status: &PWStatusBytes,
|
||||||
|
long: bool,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let data = pw_status.serialize_for_put(long);
|
||||||
|
|
||||||
|
let cmd = commands::put_pw_status(data);
|
||||||
|
apdu::send_command(self.tx(), cmd, false)?.try_into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set cardholder certificate (for AUT, DEC or SIG).
|
||||||
|
///
|
||||||
|
/// Call select_data() before calling this fn, to select a particular
|
||||||
|
/// certificate (if the card supports multiple certificates).
|
||||||
|
pub fn set_cardholder_certificate(&mut self, data: Vec<u8>) -> Result<(), Error> {
|
||||||
|
let cmd = commands::put_cardholder_certificate(data);
|
||||||
|
apdu::send_command(self.tx(), cmd, false)?.try_into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set algorithm attributes
|
||||||
|
/// (4.4.3.9 Algorithm Attributes)
|
||||||
|
pub fn set_algorithm_attributes(
|
||||||
|
&mut self,
|
||||||
|
key_type: KeyType,
|
||||||
|
algo: &Algo,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
// Command to PUT the algorithm attributes
|
||||||
|
let cmd = commands::put_data(&[key_type.algorithm_tag()], algo.to_data_object()?);
|
||||||
|
|
||||||
|
apdu::send_command(self.tx(), cmd, false)?.try_into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set resetting code
|
||||||
|
/// (4.3.4 Resetting Code)
|
||||||
|
pub fn set_resetting_code(&mut self, resetting_code: Vec<u8>) -> Result<(), Error> {
|
||||||
|
let cmd = commands::put_data(&[0xd3], resetting_code);
|
||||||
|
apdu::send_command(self.tx(), cmd, false)?.try_into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Import an existing private key to the card.
|
||||||
|
/// (This implicitly sets the algorithm info, fingerprint and timestamp)
|
||||||
|
pub fn key_import(
|
||||||
|
&mut self,
|
||||||
|
key: Box<dyn CardUploadableKey>,
|
||||||
|
key_type: KeyType,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let algo_info = self.algorithm_information();
|
||||||
|
|
||||||
|
// An error is ok - it's fine if a card doesn't offer a list of
|
||||||
|
// supported algorithms
|
||||||
|
let algo_info = algo_info.unwrap_or(None);
|
||||||
|
|
||||||
|
keys::key_import(self, key, key_type, algo_info)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate a key on the card.
|
||||||
|
/// (7.2.14 GENERATE ASYMMETRIC KEY PAIR)
|
||||||
|
///
|
||||||
|
/// If the `algo` parameter is Some, then this algorithm will be set on
|
||||||
|
/// the card for "key_type".
|
||||||
|
///
|
||||||
|
/// Note: `algo` needs to precisely specify the RSA bitsize of e (if
|
||||||
|
/// applicable), and import format, with values that the current card
|
||||||
|
/// supports.
|
||||||
|
pub fn generate_key(
|
||||||
|
&mut self,
|
||||||
|
fp_from_pub: fn(
|
||||||
|
&PublicKeyMaterial,
|
||||||
|
KeyGenerationTime,
|
||||||
|
KeyType,
|
||||||
|
) -> Result<Fingerprint, Error>,
|
||||||
|
key_type: KeyType,
|
||||||
|
algo: Option<&Algo>,
|
||||||
|
) -> Result<(PublicKeyMaterial, KeyGenerationTime), Error> {
|
||||||
|
keys::gen_key_with_metadata(self, fp_from_pub, key_type, algo)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate a key on the card.
|
||||||
|
/// (7.2.14 GENERATE ASYMMETRIC KEY PAIR)
|
||||||
|
///
|
||||||
|
/// This is a wrapper around generate_key() which allows
|
||||||
|
/// using the simplified `AlgoSimple` algorithm selector enum.
|
||||||
|
///
|
||||||
|
/// Note: AlgoSimple doesn't specify card specific details (such as
|
||||||
|
/// bitsize of e for RSA, and import format). This function determines
|
||||||
|
/// these values based on information from the card.
|
||||||
|
pub fn generate_key_simple(
|
||||||
|
&mut self,
|
||||||
|
fp_from_pub: fn(
|
||||||
|
&PublicKeyMaterial,
|
||||||
|
KeyGenerationTime,
|
||||||
|
KeyType,
|
||||||
|
) -> Result<Fingerprint, Error>,
|
||||||
|
key_type: KeyType,
|
||||||
|
simple: AlgoSimple,
|
||||||
|
) -> Result<(PublicKeyMaterial, KeyGenerationTime), Error> {
|
||||||
|
let ard = self.application_related_data()?;
|
||||||
|
let algo_info = if let Ok(ai) = self.algorithm_information() {
|
||||||
|
ai
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let algo = simple.determine_algo(key_type, &ard, algo_info)?;
|
||||||
|
|
||||||
|
Self::generate_key(self, fp_from_pub, key_type, Some(&algo))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get public key material from the card.
|
||||||
|
///
|
||||||
|
/// Note: this fn returns a set of raw public key data (not an
|
||||||
|
/// OpenPGP data structure).
|
||||||
|
///
|
||||||
|
/// Note also that the information from the card is insufficient to
|
||||||
|
/// reconstruct a pre-existing OpenPGP public key that corresponds to
|
||||||
|
/// the private key on the card.
|
||||||
|
pub fn public_key(&mut self, key_type: KeyType) -> Result<PublicKeyMaterial, Error> {
|
||||||
|
keys::public_key(self, key_type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn digestinfo(hash: Hash) -> Vec<u8> {
|
||||||
|
match hash {
|
||||||
|
Hash::SHA256(_) | Hash::SHA384(_) | Hash::SHA512(_) => {
|
||||||
|
let tlv = Tlv::new(
|
||||||
|
[0x30],
|
||||||
|
Value::C(vec![
|
||||||
|
Tlv::new(
|
||||||
|
[0x30],
|
||||||
|
Value::C(vec![
|
||||||
|
Tlv::new(
|
||||||
|
[0x06],
|
||||||
|
// unwrapping is ok, for SHA*
|
||||||
|
Value::S(hash.oid().unwrap().to_vec()),
|
||||||
|
),
|
||||||
|
Tlv::new([0x05], Value::S(vec![])),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
Tlv::new([0x04], Value::S(hash.digest().to_vec())),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
tlv.serialize()
|
||||||
|
}
|
||||||
|
Hash::EdDSA(d) => d.to_vec(),
|
||||||
|
Hash::ECDSA(d) => d.to_vec(),
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,7 +4,7 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
|
|
||||||
use openpgp_card::{CardBackend, Error, StatusBytes};
|
use openpgp_card::{Error, OpenPgp, StatusBytes};
|
||||||
use openpgp_card_pcsc::PcscBackend;
|
use openpgp_card_pcsc::PcscBackend;
|
||||||
use openpgp_card_sequoia::card::Open;
|
use openpgp_card_sequoia::card::Open;
|
||||||
|
|
||||||
|
@ -16,12 +16,13 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let cli = cli::Cli::from_args();
|
let cli = cli::Cli::from_args();
|
||||||
|
|
||||||
let mut card = PcscBackend::open_by_ident(&cli.ident, None)?;
|
let mut card = PcscBackend::open_by_ident(&cli.ident, None)?;
|
||||||
let mut txc = card.transaction()?;
|
let mut pgp = OpenPgp::new(&mut card);
|
||||||
|
|
||||||
let pinpad_verify = txc.feature_pinpad_verify();
|
let pgpt = pgp.transaction()?;
|
||||||
let pinpad_modify = txc.feature_pinpad_modify();
|
let pinpad_verify = pgpt.feature_pinpad_verify();
|
||||||
|
let pinpad_modify = pgpt.feature_pinpad_modify();
|
||||||
|
|
||||||
let mut open = Open::new(&mut *txc)?;
|
let mut open = Open::new(pgpt)?;
|
||||||
|
|
||||||
match cli.cmd {
|
match cli.cmd {
|
||||||
cli::Command::SetUserPin {} => {
|
cli::Command::SetUserPin {} => {
|
||||||
|
|
|
@ -13,8 +13,7 @@ use sequoia_openpgp::Cert;
|
||||||
|
|
||||||
use openpgp_card::algorithm::AlgoSimple;
|
use openpgp_card::algorithm::AlgoSimple;
|
||||||
use openpgp_card::card_do::Sex;
|
use openpgp_card::card_do::Sex;
|
||||||
use openpgp_card::{CardBackend, KeyType};
|
use openpgp_card::{CardBackend, KeyType, OpenPgp};
|
||||||
|
|
||||||
use openpgp_card_sequoia::card::{Admin, Open};
|
use openpgp_card_sequoia::card::{Admin, Open};
|
||||||
use openpgp_card_sequoia::util::{make_cert, public_key_material_to_key};
|
use openpgp_card_sequoia::util::{make_cert, public_key_material_to_key};
|
||||||
use openpgp_card_sequoia::{sq_util, PublicKey};
|
use openpgp_card_sequoia::{sq_util, PublicKey};
|
||||||
|
@ -71,9 +70,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
cmd,
|
cmd,
|
||||||
} => {
|
} => {
|
||||||
let mut card = util::open_card(&ident)?;
|
let mut card = util::open_card(&ident)?;
|
||||||
let mut txc = card.transaction()?;
|
let mut pgp = OpenPgp::new(&mut card);
|
||||||
|
|
||||||
let mut open = Open::new(&mut *txc)?;
|
let mut open = Open::new(pgp.transaction()?)?;
|
||||||
|
|
||||||
match cmd {
|
match cmd {
|
||||||
cli::AdminCommand::Name { name } => {
|
cli::AdminCommand::Name { name } => {
|
||||||
|
@ -134,9 +133,8 @@ fn list_cards() -> Result<()> {
|
||||||
println!("Available OpenPGP cards:");
|
println!("Available OpenPGP cards:");
|
||||||
|
|
||||||
for mut card in cards {
|
for mut card in cards {
|
||||||
let mut txc = card.transaction()?;
|
let mut pgp = OpenPgp::new(&mut card);
|
||||||
|
let open = Open::new(pgp.transaction()?)?;
|
||||||
let open = Open::new(&mut *txc)?;
|
|
||||||
println!(" {}", open.application_identifier()?.ident());
|
println!(" {}", open.application_identifier()?.ident());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -147,9 +145,10 @@ fn list_cards() -> Result<()> {
|
||||||
|
|
||||||
fn set_identity(ident: &str, id: u8) -> Result<(), Box<dyn std::error::Error>> {
|
fn set_identity(ident: &str, id: u8) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let mut card = util::open_card(ident)?;
|
let mut card = util::open_card(ident)?;
|
||||||
let mut txc = card.transaction()?;
|
let mut pgp = OpenPgp::new(&mut card);
|
||||||
|
|
||||||
txc.set_identity(id)?;
|
let mut pgpt = pgp.transaction()?;
|
||||||
|
pgpt.set_identity(id)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -166,9 +165,8 @@ fn print_status(ident: Option<String>, verbose: bool) -> Result<()> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut txc = card.transaction()?;
|
let mut pgp = OpenPgp::new(&mut *card);
|
||||||
|
let mut open = Open::new(pgp.transaction()?)?;
|
||||||
let mut open = Open::new(&mut *txc)?;
|
|
||||||
|
|
||||||
let ident = open.application_identifier()?.ident();
|
let ident = open.application_identifier()?.ident();
|
||||||
|
|
||||||
|
@ -326,9 +324,9 @@ fn decrypt(
|
||||||
let input = util::open_or_stdin(input.as_deref())?;
|
let input = util::open_or_stdin(input.as_deref())?;
|
||||||
|
|
||||||
let mut card = util::open_card(ident)?;
|
let mut card = util::open_card(ident)?;
|
||||||
let mut txc = card.transaction()?;
|
let mut pgp = OpenPgp::new(&mut card);
|
||||||
|
|
||||||
let mut open = Open::new(&mut *txc)?;
|
let mut open = Open::new(pgp.transaction()?)?;
|
||||||
|
|
||||||
let mut user = util::verify_to_user(&mut open, pin_file)?;
|
let mut user = util::verify_to_user(&mut open, pin_file)?;
|
||||||
let d = user.decryptor(&cert)?;
|
let d = user.decryptor(&cert)?;
|
||||||
|
@ -352,9 +350,9 @@ fn sign_detached(
|
||||||
let mut input = util::open_or_stdin(input.as_deref())?;
|
let mut input = util::open_or_stdin(input.as_deref())?;
|
||||||
|
|
||||||
let mut card = util::open_card(ident)?;
|
let mut card = util::open_card(ident)?;
|
||||||
let mut txc = card.transaction()?;
|
let mut pgp = OpenPgp::new(&mut card);
|
||||||
|
|
||||||
let mut open = Open::new(&mut *txc)?;
|
let mut open = Open::new(pgp.transaction()?)?;
|
||||||
|
|
||||||
let mut sign = util::verify_to_sign(&mut open, pin_file)?;
|
let mut sign = util::verify_to_sign(&mut open, pin_file)?;
|
||||||
let s = sign.signer(&cert)?;
|
let s = sign.signer(&cert)?;
|
||||||
|
@ -371,9 +369,10 @@ fn sign_detached(
|
||||||
fn factory_reset(ident: &str) -> Result<()> {
|
fn factory_reset(ident: &str) -> Result<()> {
|
||||||
println!("Resetting Card {}", ident);
|
println!("Resetting Card {}", ident);
|
||||||
let mut card = util::open_card(ident)?;
|
let mut card = util::open_card(ident)?;
|
||||||
let mut txc = card.transaction()?;
|
let mut pgp = OpenPgp::new(&mut card);
|
||||||
|
|
||||||
Open::new(&mut *txc)?.factory_reset()
|
let mut open = Open::new(pgp.transaction()?)?;
|
||||||
|
open.factory_reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn key_import_yolo(mut admin: Admin, key: &Cert) -> Result<()> {
|
fn key_import_yolo(mut admin: Admin, key: &Cert) -> Result<()> {
|
||||||
|
|
|
@ -19,7 +19,7 @@ pub(crate) fn open_card(ident: &str) -> Result<impl CardBackend, Error> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn verify_to_user<'app, 'open>(
|
pub(crate) fn verify_to_user<'app, 'open>(
|
||||||
open: &'app mut Open<'app>,
|
open: &'open mut Open<'app>,
|
||||||
pin_file: Option<PathBuf>,
|
pin_file: Option<PathBuf>,
|
||||||
) -> Result<User<'app, 'open>, Box<dyn std::error::Error>> {
|
) -> Result<User<'app, 'open>, Box<dyn std::error::Error>> {
|
||||||
if let Some(path) = pin_file {
|
if let Some(path) = pin_file {
|
||||||
|
@ -37,7 +37,7 @@ pub(crate) fn verify_to_user<'app, 'open>(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn verify_to_sign<'app, 'open>(
|
pub(crate) fn verify_to_sign<'app, 'open>(
|
||||||
open: &'app mut Open<'app>,
|
open: &'open mut Open<'app>,
|
||||||
pin_file: Option<PathBuf>,
|
pin_file: Option<PathBuf>,
|
||||||
) -> Result<Sign<'app, 'open>, Box<dyn std::error::Error>> {
|
) -> Result<Sign<'app, 'open>, Box<dyn std::error::Error>> {
|
||||||
if let Some(path) = pin_file {
|
if let Some(path) = pin_file {
|
||||||
|
|
Loading…
Reference in a new issue