Implement a "pubkey" command that prints the OpenPGP public key representation of the keys on a card.
This commit is contained in:
parent
0c7ceab2b9
commit
2f903f5907
3 changed files with 145 additions and 33 deletions
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue