From 22c29262d3269f572fb4ce2e6f170e4624bfc1de Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Mon, 23 May 2022 17:52:33 +0200 Subject: [PATCH] Implement attestation-related functionality in opgpcard --- tools/Cargo.toml | 1 + tools/src/bin/opgpcard/cli.rs | 37 ++++++++++++++++++ tools/src/bin/opgpcard/main.rs | 71 ++++++++++++++++++++++++++++++++++ tools/src/bin/opgpcard/util.rs | 11 ++++++ 4 files changed, 120 insertions(+) diff --git a/tools/Cargo.toml b/tools/Cargo.toml index b1cfb4b..b58ac0b 100644 --- a/tools/Cargo.toml +++ b/tools/Cargo.toml @@ -17,6 +17,7 @@ openpgp-card = { path = "../openpgp-card", version = "0.2.4" } openpgp-card-pcsc = { path = "../pcsc", version = "0.2" } openpgp-card-sequoia = { path = "../openpgp-card-sequoia", version = "0.0.11" } sshkeys = "0.3.2" +pem = "1" rpassword = "6" anyhow = "1" clap = { version = "3.1", features = ["derive"] } diff --git a/tools/src/bin/opgpcard/cli.rs b/tools/src/bin/opgpcard/cli.rs index 3bddf1f..33b117d 100644 --- a/tools/src/bin/opgpcard/cli.rs +++ b/tools/src/bin/opgpcard/cli.rs @@ -109,6 +109,12 @@ pub enum Command { input: Option, }, + /// Attestation management (Yubico) + Attestation { + #[clap(subcommand)] + cmd: AttCommand, + }, + /// Completely reset a card (deletes all data, including the keys on the card!) FactoryReset { #[clap(name = "card ident", short = 'c', long = "card")] @@ -221,3 +227,34 @@ pub enum PinCommand { user_pin_new: Option, }, } + +#[derive(Parser, Debug)] +pub enum AttCommand { + /// Print the card's "Attestation Certificate" + Cert { + #[clap(name = "card ident", short = 'c', long = "card")] + ident: Option, + }, + + /// Generate "Attestation Statement" for one of the key slots on the card + Generate { + #[clap(name = "card ident", short = 'c', long = "card")] + ident: String, + + #[clap(name = "Key slot (SIG|DEC|AUT)", short = 'k', long = "key")] + key: String, + + #[clap(name = "User PIN file", short = 'p', long = "user-pin")] + user_pin: Option, + }, + + /// Print a "cardholder certificate" from the card. + /// This shows the "Attestation Statement", if one has been generated. + Statement { + #[clap(name = "card ident", short = 'c', long = "card")] + ident: Option, + + #[clap(name = "Key slot (SIG|DEC|AUT)", short = 'k', long = "key")] + key: String, + }, +} diff --git a/tools/src/bin/opgpcard/main.rs b/tools/src/bin/opgpcard/main.rs index 59a6f55..9edea2c 100644 --- a/tools/src/bin/opgpcard/main.rs +++ b/tools/src/bin/opgpcard/main.rs @@ -78,6 +78,77 @@ fn main() -> Result<(), Box> { ); } } + cli::Command::Attestation { cmd } => match cmd { + cli::AttCommand::Cert { ident } => { + let mut card = pick_card_for_reading(ident)?; + + let mut pgp = OpenPgp::new(&mut *card); + let mut open = Open::new(pgp.transaction()?)?; + + if let Ok(ac) = open.attestation_certificate() { + let pem = util::pem_encode(ac); + println!("{}", pem); + } + } + cli::AttCommand::Generate { + ident, + key, + user_pin, + } => { + let mut card = util::open_card(&ident)?; + let mut pgp = OpenPgp::new(&mut card); + + let mut open = Open::new(pgp.transaction()?)?; + let user_pin = util::get_pin(&mut open, user_pin, ENTER_USER_PIN); + + let mut sign = util::verify_to_sign(&mut open, user_pin.as_deref())?; + + let kt = match key.as_str() { + "SIG" => KeyType::Signing, + "DEC" => KeyType::Decryption, + "AUT" => KeyType::Authentication, + _ => { + return Err(anyhow!("Unexpected Key Type {}", key).into()); + } + }; + sign.generate_attestation(kt)?; + } + cli::AttCommand::Statement { ident, key } => { + let mut card = pick_card_for_reading(ident)?; + + let mut pgp = OpenPgp::new(&mut *card); + let mut open = Open::new(pgp.transaction()?)?; + + // Load cardholder certificate from card. + + // FIXME/Note: SELECT_DATA seemed to not work as expected on YK5, + let cert = match key.as_str() { + "AUT" => open.cardholder_certificate()?, + "DEC" => { + // skip first cardholder certificate + let _ = open.cardholder_certificate()?; + open.next_cardholder_certificate()? + } + "SIG" => { + // skip first two cardholder certificates + let _ = open.cardholder_certificate()?; + let _ = open.next_cardholder_certificate()?; + open.next_cardholder_certificate()? + } + + _ => { + return Err(anyhow!("Unexpected Key Type {}", key).into()); + } + }; + + if !cert.is_empty() { + let pem = util::pem_encode(cert); + println!("{}", pem); + } else { + println!("Cardholder certificate slot is empty"); + } + } + }, cli::Command::FactoryReset { ident } => { factory_reset(&ident)?; } diff --git a/tools/src/bin/opgpcard/util.rs b/tools/src/bin/opgpcard/util.rs index 6de96aa..918b085 100644 --- a/tools/src/bin/opgpcard/util.rs +++ b/tools/src/bin/opgpcard/util.rs @@ -231,3 +231,14 @@ pub(crate) fn print_gnuk_note(err: Error, card: &Open) -> Result<()> { } Ok(()) } + +pub(crate) fn pem_encode(data: Vec) -> String { + const PEM_TAG: &str = "CERTIFICATE"; + + let pem = pem::Pem { + tag: String::from(PEM_TAG), + contents: data, + }; + + pem::encode(&pem) +}