From 2f903f5907a9dbfafdc8b1cd2ef11b01cd4590ce Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Sun, 3 Apr 2022 00:58:32 +0200 Subject: [PATCH] Implement a "pubkey" command that prints the OpenPGP public key representation of the keys on a card. --- tools/README.md | 48 +++++++++++++ tools/src/bin/opgpcard/cli.rs | 7 ++ tools/src/bin/opgpcard/main.rs | 123 ++++++++++++++++++++++++--------- 3 files changed, 145 insertions(+), 33 deletions(-) diff --git a/tools/README.md b/tools/README.md index 59e6952..368da87 100644 --- a/tools/README.md +++ b/tools/README.md @@ -141,6 +141,54 @@ AUT: Ed25519 (EdDSA) AUT: Ed448 (EdDSA) ``` +### Get OpenPGP public key + +It is possible to get an OpenPGP public key representation of the keys on a card in many (but not all) circumstances. + +This command will always return an OpenPGP public key representation, however, eliptic curve-based decryption +(sub-)keys may be wrong (see https://gitlab.com/hkos/openpgp-card/-/issues/2). + +``` +$ opgpcard pubkey +OpenPGP card ABCD:01234567 +Enter user PIN: +-----BEGIN PGP PUBLIC KEY BLOCK----- +Comment: F9C7 97CB 1AF2 1C68 AEEC 8D4D 1002 89F5 5EF6 B2D4 +Comment: baz + +xjMEYkOmahYJKwYBBAHaRw8BAQdADwHIuuSgboyzgcLci8Hc0Q15YHKfDP8/CZG4 +uumYosXNA2JhesLABgQTFgoAeAWCYkjTagWJAAAAAAkQEAKJ9V72stRHFAAAAAAA +HgAgc2FsdEBub3RhdGlvbnMuc2VxdW9pYS1wZ3Aub3JnifpLw5yhNlKffk7V+P9g +idnIM3j6l3k34+p7tMQmCPoCmwMWIQT5x5fLGvIcaK7sjU0QAon1Xvay1AAAhJkB +AIEhZTDuc9xARVK8ta51SOpX3mZs/UYA5a+UrB6vpmZ3AP4k14gFQ6q/cl/SOhPR +FpCAvYlqL8rb3gc2sFIZDfYUDM4zBGJDpmoWCSsGAQQB2kcPAQEHQDRodITykZoi +hIIPZcFZ2bMXvo20YEv+I1eg2kFQ2qSqwsAGBBgWCgB4BYJiSNNqBYkAAAAACRAQ +Aon1Xvay1EcUAAAAAAAeACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmcI +5rVHhWA5cGdYlyQJYRXv4osAyFlyznFiUOATnoT6LgKbIBYhBPnHl8sa8hxoruyN +TRACifVe9rLUAADpTwD/a+AlBGryfLsqFzIhdJRpGkoOl0H+xcgk3vcaPUQq0pcA +/3TtUmaJ5w60qb/Px7/Q+MTymHH54elRY4lvwIfbvkUIzjgEYkOmahIKKwYBBAGX +VQEFAQEHQO5KBZ7cMwwjsXGOWWMqgAkCyNdw7smcx/+jBEk0m38dAwEKCcLABgQY +FgoAeAWCYkjTagWJAAAAAAkQEAKJ9V72stRHFAAAAAAAHgAgc2FsdEBub3RhdGlv +bnMuc2VxdW9pYS1wZ3Aub3Jn9IwQkbcw9W0jfrduv1q4qNhsOgJWkGTMbVyvQCug +YpcCmwwWIQT5x5fLGvIcaK7sjU0QAon1Xvay1AAAfTwBAPSQq/hGcGjAWNePHoLH +5zA/ePu1vaY1nh2dPhqtUg8+AP0TDG96MJxlM8SJUQXtQsJCAEo4qT9GnGi7MyTU +nvraDw== +=es4l +-----END PGP PUBLIC KEY BLOCK----- +``` + +You can query a specific card + +``` +$ opgpcard pubkey -c ABCD:01234567 +``` + +And/or pass the user PIN as a file, for non-interactive use": + +``` +$ opgpcard pubkey -p +``` + ### Using a card for ssh auth To use an OpenPGP card for ssh login authentication, a PGP authentication key needs to exist on the card. diff --git a/tools/src/bin/opgpcard/cli.rs b/tools/src/bin/opgpcard/cli.rs index 42e88ec..026c62d 100644 --- a/tools/src/bin/opgpcard/cli.rs +++ b/tools/src/bin/opgpcard/cli.rs @@ -31,6 +31,13 @@ pub enum Command { #[clap(name = "card ident", short = 'c', long = "card")] ident: Option, }, + Pubkey { + #[clap(name = "card ident", short = 'c', long = "card")] + ident: Option, + + #[clap(name = "User PIN file", short = 'p', long = "user-pin")] + user_pin: Option, + }, FactoryReset { #[clap(name = "card ident", short = 'c', long = "card")] ident: String, diff --git a/tools/src/bin/opgpcard/main.rs b/tools/src/bin/opgpcard/main.rs index 04dbae5..76da734 100644 --- a/tools/src/bin/opgpcard/main.rs +++ b/tools/src/bin/opgpcard/main.rs @@ -42,6 +42,9 @@ fn main() -> Result<(), Box> { cli::Command::Ssh { ident } => { print_ssh(ident)?; } + cli::Command::Pubkey { ident, user_pin } => { + print_pubkey(ident, user_pin)?; + } cli::Command::SetIdentity { ident, id } => { set_identity(&ident, id)?; } @@ -161,13 +164,15 @@ fn set_identity(ident: &str, id: u8) -> Result<(), Box> { Ok(()) } -fn print_status(ident: Option, verbose: bool) -> Result<()> { - let mut card: Box = if let Some(ident) = ident { - Box::new(util::open_card(&ident)?) +/// Return a card for a read operation. If `ident` is None, and exactly one card +/// is plugged in, that card is returned. (We don't This +fn pick_card_for_reading(ident: Option) -> Result> { + if let Some(ident) = ident { + Ok(Box::new(util::open_card(&ident)?)) } else { let mut cards = util::cards()?; if cards.len() == 1 { - Box::new(cards.pop().unwrap()) + Ok(Box::new(cards.pop().unwrap())) } else if cards.is_empty() { return Err(anyhow::anyhow!("No cards found")); } else { @@ -180,7 +185,11 @@ fn print_status(ident: Option, verbose: bool) -> Result<()> { return Err(anyhow::anyhow!("Specify card")); } - }; + } +} + +fn print_status(ident: Option, verbose: bool) -> Result<()> { + let mut card = pick_card_for_reading(ident)?; let mut pgp = OpenPgp::new(&mut *card); let mut open = Open::new(pgp.transaction()?)?; @@ -323,32 +332,14 @@ fn print_status(ident: Option, verbose: bool) -> Result<()> { } fn print_ssh(ident: Option) -> Result<()> { - let mut card: Box = if let Some(ident) = ident { - Box::new(util::open_card(&ident)?) - } else { - let mut cards = util::cards()?; - if cards.len() == 1 { - Box::new(cards.pop().unwrap()) - } else if cards.is_empty() { - return Err(anyhow::anyhow!("No cards found")); - } else { - println!("Found {} cards:", cards.len()); - list_cards()?; - - println!(); - println!("Specify which card to use with '--card '"); - println!(); - - return Err(anyhow::anyhow!("Specify card")); - } - }; + let mut card = pick_card_for_reading(ident)?; let mut pgp = OpenPgp::new(&mut *card); let mut open = Open::new(pgp.transaction()?)?; let ident = open.application_identifier()?.ident(); - println!("OpenPGP card {}", open.application_identifier()?.ident()); + println!("OpenPGP card {}", ident); // Print fingerprint of authentication subkey let fps = open.fingerprints()?; @@ -369,6 +360,60 @@ fn print_ssh(ident: Option) -> Result<()> { Ok(()) } +fn print_pubkey(ident: Option, user_pin: Option) -> Result<()> { + let mut card = pick_card_for_reading(ident)?; + + let mut pgp = OpenPgp::new(&mut *card); + let mut open = Open::new(pgp.transaction()?)?; + + let ident = open.application_identifier()?.ident(); + + println!("OpenPGP card {}", ident); + + let user_pin = util::get_pin(&mut open, user_pin, ENTER_USER_PIN); + + let pkm = open.public_key(KeyType::Signing)?; + let times = open.key_generation_times()?; + + let key_sig = public_key_material_to_key( + &pkm, + KeyType::Signing, + *times.signature().expect("Signature time is unset"), + )?; + + let mut key_dec = None; + if let Ok(pkm) = open.public_key(KeyType::Decryption) { + if let Some(ts) = times.decryption() { + key_dec = Some(public_key_material_to_key(&pkm, KeyType::Decryption, *ts)?); + } + } + + let mut key_aut = None; + if let Ok(pkm) = open.public_key(KeyType::Authentication) { + if let Some(ts) = times.authentication() { + key_aut = Some(public_key_material_to_key( + &pkm, + KeyType::Authentication, + *ts, + )?); + } + } + + let cert = get_cert( + &mut open, + key_sig, + key_dec, + key_aut, + user_pin.as_deref(), + &|| println!("Enter user PIN on card reader pinpad."), + )?; + + let armored = String::from_utf8(cert.armored().to_vec()?)?; + println!("{}", armored); + + Ok(()) +} + fn decrypt( ident: &str, pin_file: Option, @@ -500,6 +545,24 @@ fn key_import_explicit( Ok(()) } +fn get_cert( + open: &mut Open, + key_sig: PublicKey, + key_dec: Option, + key_aut: Option, + user_pin: Option<&[u8]>, + prompt: &dyn Fn(), +) -> Result { + if user_pin.is_none() && open.feature_pinpad_verify() { + println!( + "The public cert will now be generated.\n\n\ + You will need to enter your user PIN multiple times during this process.\n\n" + ); + } + + make_cert(open, key_sig, key_dec, key_aut, user_pin, prompt) +} + fn generate_keys( mut open: Open, admin_pin: Option<&[u8]>, @@ -549,16 +612,10 @@ fn generate_keys( // 3) Generate a Cert from the generated keys. For this, we // need "signing" access to the card (to make binding signatures within // the Cert). - if user_pin.is_none() && open.feature_pinpad_verify() { - println!( - "The public cert will now be generated.\n\n\ - You will need to enter your user PIN multiple times during this process.\n\n" - ); - } - - let cert = make_cert(&mut open, key_sig, key_dec, key_aut, user_pin, &|| { + let cert = get_cert(&mut open, key_sig, key_dec, key_aut, user_pin, &|| { println!("Enter user PIN on card reader pinpad.") })?; + let armored = String::from_utf8(cert.armored().to_vec()?)?; // Write armored certificate to the output file (or stdout)