use std::path::{Path, PathBuf};
use anyhow::{anyhow, Context, Result};
use card_backend_pcsc::PcscBackend;
use openpgp_card::ocard::algorithm::{AlgorithmAttributes, Curve};
use openpgp_card::ocard::crypto::{EccType, PublicKeyMaterial};
use openpgp_card::ocard::StatusBytes;
use openpgp_card::state::{Admin, Open, Sign, Transaction, User};
use openpgp_card::Card;
use openpgp_card::Error;
use secrecy::SecretString;
pub(crate) fn cards() -> Result<Vec<Card<Open>>, Error> {
let mut cards = vec![];
for backend in PcscBackend::cards(None)? {
let card = Card::<Open>::new(backend?)?;
cards.push(card);
}
Ok(cards)
}
pub(crate) fn open_card(ident: &str) -> Result<Card<Open>, Error> {
let cards = PcscBackend::card_backends(None)?;
let card = Card::<Open>::open_by_ident(cards, ident)?;
Ok(card)
}
pub(crate) fn get_pin(
card: &mut Card<Transaction<'_>>,
pin_file: Option<PathBuf>,
msg: &str,
) -> Result<Option<SecretString>> {
if let Some(path) = pin_file {
Ok(Some(load_pin(&path).context(format!(
"Failed to read PIN file {}",
path.display()
))?))
} else if !card.feature_pinpad_verify() {
let pin = rpassword::prompt_password(msg).context("Failed to read PIN")?;
Ok(Some(pin.into()))
} else {
Ok(None)
}
}
pub(crate) fn input_pin_twice(msg1: &str, msg2: &str) -> Result<SecretString> {
let newpin1 = rpassword::prompt_password(msg1)?;
let newpin2 = rpassword::prompt_password(msg2)?;
if newpin1 != newpin2 {
Err(anyhow::anyhow!("PINs do not match."))
} else {
Ok(newpin1.into())
}
}
pub(crate) fn verify_to_user<'app, 'open>(
card: &'open mut Card<Transaction<'app>>,
pin: Option<SecretString>,
) -> Result<Card<User<'app, 'open>>, Box<dyn std::error::Error>> {
if let Some(pin) = pin {
card.verify_user_pin(pin)?;
} else {
if !card.feature_pinpad_verify() {
return Err(anyhow!("No user PIN file provided, and no pinpad found").into());
};
card.verify_user_pinpad(&|| eprintln!("Enter user PIN on card reader pinpad."))?;
}
Ok(card.to_user_card(None)?)
}
pub(crate) fn verify_to_sign<'app, 'open>(
card: &'open mut Card<Transaction<'app>>,
pin: Option<SecretString>,
) -> Result<Card<Sign<'app, 'open>>, Box<dyn std::error::Error>> {
if let Some(pin) = pin {
card.verify_user_signing_pin(pin)?;
} else {
if !card.feature_pinpad_verify() {
return Err(anyhow!("No user PIN file provided, and no pinpad found").into());
}
card.verify_user_signing_pinpad(&|| eprintln!("Enter user PIN on card reader pinpad."))?;
}
Ok(card.to_signing_card(None)?)
}
pub(crate) fn verify_to_admin<'app, 'open>(
card: &'open mut Card<Transaction<'app>>,
pin: Option<SecretString>,
) -> Result<Card<Admin<'app, 'open>>, Box<dyn std::error::Error>> {
if let Some(pin) = pin {
card.verify_admin_pin(pin)?;
} else {
if !card.feature_pinpad_verify() {
return Err(anyhow!("No admin PIN file provided, and no pinpad found").into());
}
card.verify_admin_pinpad(&|| eprintln!("Enter admin PIN on card reader pinpad."))?;
}
Ok(card.to_admin_card(None)?)
}
pub(crate) fn load_pin(pin_file: &Path) -> Result<SecretString> {
let pin = std::fs::read_to_string(pin_file)?;
Ok(pin.trim().to_string().into())
}
pub(crate) fn open_or_stdin(f: Option<&Path>) -> Result<Box<dyn std::io::Read + Send + Sync>> {
match f {
Some(f) => Ok(Box::new(
std::fs::File::open(f).context("Failed to open input file")?,
)),
None => Ok(Box::new(std::io::stdin())),
}
}
pub(crate) fn open_or_stdout(f: Option<&Path>) -> Result<Box<dyn std::io::Write + Send + Sync>> {
match f {
Some(f) => Ok(Box::new(
std::fs::File::create(f).context("Failed to open input file")?,
)),
None => Ok(Box::new(std::io::stdout())),
}
}
fn get_ssh_pubkey(pkm: &PublicKeyMaterial, ident: String) -> Result<sshkeys::PublicKey> {
let cardname = format!("opgpcard:{ident}");
let (key_type, kind) = match pkm {
PublicKeyMaterial::R(rsa) => {
let key_type = sshkeys::KeyType::from_name("ssh-rsa")?;
let kind = sshkeys::PublicKeyKind::Rsa(sshkeys::RsaPublicKey {
e: rsa.v().to_vec(),
n: rsa.n().to_vec(),
});
Ok((key_type, kind))
}
PublicKeyMaterial::E(ecc) => {
if let AlgorithmAttributes::Ecc(ecc_attrs) = ecc.algo() {
match ecc_attrs.ecc_type() {
EccType::EdDSA => {
let key_type = sshkeys::KeyType::from_name("ssh-ed25519")?;
let kind = sshkeys::PublicKeyKind::Ed25519(sshkeys::Ed25519PublicKey {
key: ecc.data().to_vec(),
sk_application: None,
});
Ok((key_type, kind))
}
EccType::ECDSA => {
let (curve, name) = match ecc_attrs.curve() {
Curve::NistP256r1 => Ok((
sshkeys::Curve::from_identifier("nistp256")?,
"ecdsa-sha2-nistp256",
)),
Curve::NistP384r1 => Ok((
sshkeys::Curve::from_identifier("nistp384")?,
"ecdsa-sha2-nistp384",
)),
Curve::NistP521r1 => Ok((
sshkeys::Curve::from_identifier("nistp521")?,
"ecdsa-sha2-nistp521",
)),
_ => Err(anyhow!("Unexpected ECDSA curve {:?}", ecc_attrs.curve())),
}?;
let key_type = sshkeys::KeyType::from_name(name)?;
let kind = sshkeys::PublicKeyKind::Ecdsa(sshkeys::EcdsaPublicKey {
curve,
key: ecc.data().to_vec(),
sk_application: None,
});
Ok((key_type, kind))
}
_ => Err(anyhow!("Unexpected EccType {:?}", ecc_attrs.ecc_type())),
}
} else {
Err(anyhow!("Unexpected Algo in EccPub {:?}", ecc))
}
}
}?;
let pk = sshkeys::PublicKey {
key_type,
comment: Some(cardname),
kind,
};
Ok(pk)
}
pub(crate) fn get_ssh_pubkey_string(pkm: &PublicKeyMaterial, ident: String) -> Result<String> {
let pk = get_ssh_pubkey(pkm, ident)?;
let mut v = vec![];
pk.write(&mut v)?;
let s = String::from_utf8_lossy(&v).to_string();
Ok(s.trim().into())
}
pub(crate) fn print_gnuk_note(err: Error, card: &mut Card<Transaction>) -> Result<()> {
if matches!(
err,
Error::CardStatus(StatusBytes::ConditionOfUseNotSatisfied)
) {
let fps = card.fingerprints()?;
if fps.signature().is_none() && fps.decryption().is_none() && fps.authentication().is_none()
{
eprintln!(
"\nNOTE: Some cards (e.g. Gnuk) don't allow \
User PIN change while no keys exist on the card."
);
}
}
Ok(())
}
pub(crate) fn pem_encode(data: Vec<u8>) -> String {
const PEM_TAG: &str = "CERTIFICATE";
let pem = pem::Pem {
tag: String::from(PEM_TAG),
contents: data,
};
pem::encode(&pem)
}
pub(crate) struct DebugWrapper<'a>(pub &'a mut (dyn std::io::Read + Send + Sync));
impl std::io::Read for DebugWrapper<'_> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
self.0.read(buf)
}
}
impl std::fmt::Debug for DebugWrapper<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DebugWrapper").finish()
}
}