Implement a "pubkey" command that prints the OpenPGP public key representation of the keys on a card.

This commit is contained in:
Heiko Schaefer 2022-04-03 00:58:32 +02:00
parent 0c7ceab2b9
commit 2f903f5907
No known key found for this signature in database
GPG key ID: 4A849A1904CCBD7D
3 changed files with 145 additions and 33 deletions

View file

@ -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 <user-pin-file>
```
### 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.

View file

@ -31,6 +31,13 @@ pub enum Command {
#[clap(name = "card ident", short = 'c', long = "card")]
ident: Option<String>,
},
Pubkey {
#[clap(name = "card ident", short = 'c', long = "card")]
ident: Option<String>,
#[clap(name = "User PIN file", short = 'p', long = "user-pin")]
user_pin: Option<PathBuf>,
},
FactoryReset {
#[clap(name = "card ident", short = 'c', long = "card")]
ident: String,

View file

@ -42,6 +42,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
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<dyn std::error::Error>> {
Ok(())
}
fn print_status(ident: Option<String>, verbose: bool) -> Result<()> {
let mut card: Box<dyn CardBackend + Send + Sync> = 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<String>) -> Result<Box<dyn CardBackend + Send + Sync>> {
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<String>, verbose: bool) -> Result<()> {
return Err(anyhow::anyhow!("Specify card"));
}
};
}
}
fn print_status(ident: Option<String>, 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<String>, verbose: bool) -> Result<()> {
}
fn print_ssh(ident: Option<String>) -> Result<()> {
let mut card: Box<dyn CardBackend + Send + Sync> = 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 <card ident>'");
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<String>) -> Result<()> {
Ok(())
}
fn print_pubkey(ident: Option<String>, user_pin: Option<PathBuf>) -> 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<PathBuf>,
@ -500,6 +545,24 @@ fn key_import_explicit(
Ok(())
}
fn get_cert(
open: &mut Open,
key_sig: PublicKey,
key_dec: Option<PublicKey>,
key_aut: Option<PublicKey>,
user_pin: Option<&[u8]>,
prompt: &dyn Fn(),
) -> Result<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"
);
}
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)