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)
|
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
|
### 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.
|
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")]
|
#[clap(name = "card ident", short = 'c', long = "card")]
|
||||||
ident: Option<String>,
|
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 {
|
FactoryReset {
|
||||||
#[clap(name = "card ident", short = 'c', long = "card")]
|
#[clap(name = "card ident", short = 'c', long = "card")]
|
||||||
ident: String,
|
ident: String,
|
||||||
|
|
|
@ -42,6 +42,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
cli::Command::Ssh { ident } => {
|
cli::Command::Ssh { ident } => {
|
||||||
print_ssh(ident)?;
|
print_ssh(ident)?;
|
||||||
}
|
}
|
||||||
|
cli::Command::Pubkey { ident, user_pin } => {
|
||||||
|
print_pubkey(ident, user_pin)?;
|
||||||
|
}
|
||||||
cli::Command::SetIdentity { ident, id } => {
|
cli::Command::SetIdentity { ident, id } => {
|
||||||
set_identity(&ident, id)?;
|
set_identity(&ident, id)?;
|
||||||
}
|
}
|
||||||
|
@ -161,13 +164,15 @@ fn set_identity(ident: &str, id: u8) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_status(ident: Option<String>, verbose: bool) -> Result<()> {
|
/// Return a card for a read operation. If `ident` is None, and exactly one card
|
||||||
let mut card: Box<dyn CardBackend + Send + Sync> = if let Some(ident) = ident {
|
/// is plugged in, that card is returned. (We don't This
|
||||||
Box::new(util::open_card(&ident)?)
|
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 {
|
} else {
|
||||||
let mut cards = util::cards()?;
|
let mut cards = util::cards()?;
|
||||||
if cards.len() == 1 {
|
if cards.len() == 1 {
|
||||||
Box::new(cards.pop().unwrap())
|
Ok(Box::new(cards.pop().unwrap()))
|
||||||
} else if cards.is_empty() {
|
} else if cards.is_empty() {
|
||||||
return Err(anyhow::anyhow!("No cards found"));
|
return Err(anyhow::anyhow!("No cards found"));
|
||||||
} else {
|
} else {
|
||||||
|
@ -180,7 +185,11 @@ fn print_status(ident: Option<String>, verbose: bool) -> Result<()> {
|
||||||
|
|
||||||
return Err(anyhow::anyhow!("Specify card"));
|
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 pgp = OpenPgp::new(&mut *card);
|
||||||
let mut open = Open::new(pgp.transaction()?)?;
|
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<()> {
|
fn print_ssh(ident: Option<String>) -> Result<()> {
|
||||||
let mut card: Box<dyn CardBackend + Send + Sync> = if let Some(ident) = ident {
|
let mut card = pick_card_for_reading(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 pgp = OpenPgp::new(&mut *card);
|
let mut pgp = OpenPgp::new(&mut *card);
|
||||||
let mut open = Open::new(pgp.transaction()?)?;
|
let mut open = Open::new(pgp.transaction()?)?;
|
||||||
|
|
||||||
let ident = open.application_identifier()?.ident();
|
let ident = open.application_identifier()?.ident();
|
||||||
|
|
||||||
println!("OpenPGP card {}", open.application_identifier()?.ident());
|
println!("OpenPGP card {}", ident);
|
||||||
|
|
||||||
// Print fingerprint of authentication subkey
|
// Print fingerprint of authentication subkey
|
||||||
let fps = open.fingerprints()?;
|
let fps = open.fingerprints()?;
|
||||||
|
@ -369,6 +360,60 @@ fn print_ssh(ident: Option<String>) -> Result<()> {
|
||||||
Ok(())
|
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(
|
fn decrypt(
|
||||||
ident: &str,
|
ident: &str,
|
||||||
pin_file: Option<PathBuf>,
|
pin_file: Option<PathBuf>,
|
||||||
|
@ -500,6 +545,24 @@ fn key_import_explicit(
|
||||||
Ok(())
|
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(
|
fn generate_keys(
|
||||||
mut open: Open,
|
mut open: Open,
|
||||||
admin_pin: Option<&[u8]>,
|
admin_pin: Option<&[u8]>,
|
||||||
|
@ -549,16 +612,10 @@ fn generate_keys(
|
||||||
// 3) Generate a Cert from the generated keys. For this, we
|
// 3) Generate a Cert from the generated keys. For this, we
|
||||||
// need "signing" access to the card (to make binding signatures within
|
// need "signing" access to the card (to make binding signatures within
|
||||||
// the Cert).
|
// the Cert).
|
||||||
if user_pin.is_none() && open.feature_pinpad_verify() {
|
let cert = get_cert(&mut open, key_sig, key_dec, key_aut, user_pin, &|| {
|
||||||
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, &|| {
|
|
||||||
println!("Enter user PIN on card reader pinpad.")
|
println!("Enter user PIN on card reader pinpad.")
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let armored = String::from_utf8(cert.armored().to_vec()?)?;
|
let armored = String::from_utf8(cert.armored().to_vec()?)?;
|
||||||
|
|
||||||
// Write armored certificate to the output file (or stdout)
|
// Write armored certificate to the output file (or stdout)
|
||||||
|
|
Loading…
Reference in a new issue