Add --list comments to identity output

This improves the output of --generate and --identity, as well as the
interactive TUI.

Closes str4d/age-plugin-yubikey#24.
This commit is contained in:
Jack Grigg
2021-04-25 12:10:35 +12:00
parent 72c5278de0
commit 64b0ab4e16
3 changed files with 123 additions and 94 deletions
+4 -4
View File
@@ -10,7 +10,7 @@ use yubikey_piv::{
use crate::{ use crate::{
error::Error, error::Error,
p256::Recipient, p256::Recipient,
util::POLICY_EXTENSION_OID, util::{Metadata, POLICY_EXTENSION_OID},
yubikey::{self, Stub}, yubikey::{self, Stub},
BINARY_NAME, USABLE_SLOTS, BINARY_NAME, USABLE_SLOTS,
}; };
@@ -57,7 +57,7 @@ impl IdentityBuilder {
self self
} }
pub(crate) fn build(self, yubikey: &mut YubiKey) -> Result<(Stub, Recipient, String), Error> { pub(crate) fn build(self, yubikey: &mut YubiKey) -> Result<(Stub, Recipient, Metadata), Error> {
let slot = match self.slot { let slot = match self.slot {
Some(slot) => { Some(slot) => {
if !self.force { if !self.force {
@@ -141,12 +141,12 @@ impl IdentityBuilder {
)?; )?;
let (_, cert) = x509_parser::parse_x509_certificate(cert.as_ref()).unwrap(); let (_, cert) = x509_parser::parse_x509_certificate(cert.as_ref()).unwrap();
let created = cert.validity().not_before.to_rfc2822(); let metadata = Metadata::extract(yubikey, slot, &cert, false).unwrap();
Ok(( Ok((
Stub::new(yubikey.serial(), slot, &recipient), Stub::new(yubikey.serial(), slot, &recipient),
recipient, recipient,
created, metadata,
)) ))
} }
} }
+19 -32
View File
@@ -119,14 +119,14 @@ fn generate(opts: PluginOptions) -> Result<(), Error> {
let mut yubikey = yubikey::open(serial)?; let mut yubikey = yubikey::open(serial)?;
let (stub, recipient, created) = builder::IdentityBuilder::new(slot) let (stub, recipient, metadata) = builder::IdentityBuilder::new(slot)
.with_name(opts.name) .with_name(opts.name)
.with_pin_policy(pin_policy) .with_pin_policy(pin_policy)
.with_touch_policy(touch_policy) .with_touch_policy(touch_policy)
.force(opts.force) .force(opts.force)
.build(&mut yubikey)?; .build(&mut yubikey)?;
util::print_identity(stub, recipient, &created); util::print_identity(stub, recipient, metadata);
Ok(()) Ok(())
} }
@@ -173,12 +173,12 @@ fn identity(opts: PluginOptions) -> Result<(), Error> {
}?; }?;
let stub = yubikey::Stub::new(yubikey.serial(), slot, &recipient); let stub = yubikey::Stub::new(yubikey.serial(), slot, &recipient);
let created = x509_parser::parse_x509_certificate(key.certificate().as_ref()) let metadata = x509_parser::parse_x509_certificate(key.certificate().as_ref())
.ok() .ok()
.map(|(_, cert)| cert.validity().not_before.to_rfc2822()) .and_then(|(_, cert)| util::Metadata::extract(&mut yubikey, slot, &cert, true))
.unwrap_or_else(|| "Unknown".to_owned()); .unwrap();
util::print_identity(stub, recipient, &created); util::print_identity(stub, recipient, metadata);
Ok(()) Ok(())
} }
@@ -205,29 +205,15 @@ fn list(all: bool) -> Result<(), Error> {
_ => continue, _ => continue,
}; };
let ((name, pin_policy, touch_policy), created) = let metadata = match x509_parser::parse_x509_certificate(key.certificate().as_ref())
match x509_parser::parse_x509_certificate(key.certificate().as_ref()) .ok()
.ok() .and_then(|(_, cert)| util::Metadata::extract(&mut yubikey, slot, &cert, all))
.and_then(|(_, cert)| { {
util::extract_name_and_policies(&mut yubikey, &key, &cert, all) Some(res) => res,
.map(|res| (res, cert.validity().not_before.to_rfc2822())) None => continue,
}) { };
Some(res) => res,
None => continue,
};
println!( println!("{}", metadata);
"# Serial: {}, Slot: {}",
yubikey.serial(),
util::slot_to_ui(&slot),
);
println!("# Name: {}", name);
println!("# Created: {}", created);
println!("# PIN policy: {}", util::pin_policy_to_str(pin_policy));
println!(
"# Touch policy: {}",
util::touch_policy_to_str(touch_policy)
);
println!("{}", recipient.to_string()); println!("{}", recipient.to_string());
println!(); println!();
} }
@@ -358,7 +344,7 @@ fn main() -> Result<(), Error> {
}) })
.collect(); .collect();
let (stub, recipient, created) = { let (stub, recipient, metadata) = {
let (slot_index, slot) = loop { let (slot_index, slot) = loop {
match Select::new() match Select::new()
.with_prompt("🕳️ Select a slot for your age identity") .with_prompt("🕳️ Select a slot for your age identity")
@@ -391,9 +377,10 @@ fn main() -> Result<(), Error> {
let stub = yubikey::Stub::new(yubikey.serial(), slot, &recipient); let stub = yubikey::Stub::new(yubikey.serial(), slot, &recipient);
let (_, cert) = let (_, cert) =
x509_parser::parse_x509_certificate(key.certificate().as_ref()).unwrap(); x509_parser::parse_x509_certificate(key.certificate().as_ref()).unwrap();
let created = cert.validity().not_before.to_rfc2822(); let metadata =
util::Metadata::extract(&mut yubikey, slot, &cert, true).unwrap();
(stub, recipient, created) (stub, recipient, metadata)
} else { } else {
return Ok(()); return Ok(());
} }
@@ -449,7 +436,7 @@ fn main() -> Result<(), Error> {
} }
}; };
util::print_identity(stub, recipient, &created); util::print_identity(stub, recipient, metadata);
Ok(()) Ok(())
} }
+100 -58
View File
@@ -1,8 +1,10 @@
use std::fmt;
use x509_parser::{certificate::X509Certificate, der_parser::oid::Oid}; use x509_parser::{certificate::X509Certificate, der_parser::oid::Oid};
use yubikey_piv::{ use yubikey_piv::{
key::RetiredSlotId, key::{RetiredSlotId, SlotId},
policy::{PinPolicy, TouchPolicy}, policy::{PinPolicy, TouchPolicy},
Key, YubiKey, Serial, YubiKey,
}; };
use crate::{error::Error, p256::Recipient, yubikey::Stub, BINARY_NAME, USABLE_SLOTS}; use crate::{error::Error, p256::Recipient, yubikey::Stub, BINARY_NAME, USABLE_SLOTS};
@@ -89,68 +91,108 @@ pub(crate) fn extract_name(cert: &X509Certificate, all: bool) -> Option<(String,
} }
} }
pub(crate) fn extract_name_and_policies( pub(crate) struct Metadata {
yubikey: &mut YubiKey, serial: Serial,
key: &Key, slot: RetiredSlotId,
cert: &X509Certificate, name: String,
all: bool, created: String,
) -> Option<(String, Option<PinPolicy>, Option<TouchPolicy>)> { pin_policy: Option<PinPolicy>,
// We store the PIN and touch policies for identities in their certificates touch_policy: Option<TouchPolicy>,
// using the same certificate extension as PIV attestations.
// https://developers.yubico.com/PIV/Introduction/PIV_attestation.html
let policies = |c: &X509Certificate| {
c.extensions()
.get(&Oid::from(POLICY_EXTENSION_OID).unwrap())
// If the encoded extension doesn't have 2 bytes, we assume it is invalid.
.filter(|policy| policy.value.len() >= 2)
.map(|policy| {
// We should only ever see one of three values for either policy, but
// handle unknown values just in case.
let pin_policy = match policy.value[0] {
0x01 => Some(PinPolicy::Never),
0x02 => Some(PinPolicy::Once),
0x03 => Some(PinPolicy::Always),
_ => None,
};
let touch_policy = match policy.value[1] {
0x01 => Some(TouchPolicy::Never),
0x02 => Some(TouchPolicy::Always),
0x03 => Some(TouchPolicy::Cached),
_ => None,
};
(pin_policy, touch_policy)
})
.unwrap_or((None, None))
};
extract_name(cert, all).map(|(name, ours)| {
if ours {
let (pin_policy, touch_policy) = policies(&cert);
(name, pin_policy, touch_policy)
} else {
// We can extract the PIN and touch policies via an attestation. This
// is slow, but the user has asked for all compatible keys, so...
let (pin_policy, touch_policy) = yubikey_piv::key::attest(yubikey, key.slot())
.ok()
.and_then(|buf| {
x509_parser::parse_x509_certificate(&buf)
.map(|(_, c)| policies(&c))
.ok()
})
.unwrap_or((None, None));
(name, pin_policy, touch_policy)
}
})
} }
pub(crate) fn print_identity(stub: Stub, recipient: Recipient, created: &str) { impl Metadata {
pub(crate) fn extract(
yubikey: &mut YubiKey,
slot: RetiredSlotId,
cert: &X509Certificate,
all: bool,
) -> Option<Self> {
// We store the PIN and touch policies for identities in their certificates
// using the same certificate extension as PIV attestations.
// https://developers.yubico.com/PIV/Introduction/PIV_attestation.html
let policies = |c: &X509Certificate| {
c.extensions()
.get(&Oid::from(POLICY_EXTENSION_OID).unwrap())
// If the encoded extension doesn't have 2 bytes, we assume it is invalid.
.filter(|policy| policy.value.len() >= 2)
.map(|policy| {
// We should only ever see one of three values for either policy, but
// handle unknown values just in case.
let pin_policy = match policy.value[0] {
0x01 => Some(PinPolicy::Never),
0x02 => Some(PinPolicy::Once),
0x03 => Some(PinPolicy::Always),
_ => None,
};
let touch_policy = match policy.value[1] {
0x01 => Some(TouchPolicy::Never),
0x02 => Some(TouchPolicy::Always),
0x03 => Some(TouchPolicy::Cached),
_ => None,
};
(pin_policy, touch_policy)
})
.unwrap_or((None, None))
};
extract_name(cert, all)
.map(|(name, ours)| {
if ours {
let (pin_policy, touch_policy) = policies(&cert);
(name, pin_policy, touch_policy)
} else {
// We can extract the PIN and touch policies via an attestation. This
// is slow, but the user has asked for all compatible keys, so...
let (pin_policy, touch_policy) =
yubikey_piv::key::attest(yubikey, SlotId::Retired(slot))
.ok()
.and_then(|buf| {
x509_parser::parse_x509_certificate(&buf)
.map(|(_, c)| policies(&c))
.ok()
})
.unwrap_or((None, None));
(name, pin_policy, touch_policy)
}
})
.map(|(name, pin_policy, touch_policy)| Metadata {
serial: yubikey.serial(),
slot,
name,
created: cert.validity().not_before.to_rfc2822(),
pin_policy,
touch_policy,
})
}
}
impl fmt::Display for Metadata {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(
f,
"# Serial: {}, Slot: {}",
self.serial,
slot_to_ui(&self.slot)
)?;
writeln!(f, "# Name: {}", self.name)?;
writeln!(f, "# Created: {}", self.created)?;
writeln!(f, "# PIN policy: {}", pin_policy_to_str(self.pin_policy))?;
write!(
f,
"# Touch policy: {}",
touch_policy_to_str(self.touch_policy)
)
}
}
pub(crate) fn print_identity(stub: Stub, recipient: Recipient, metadata: Metadata) {
let recipient = recipient.to_string(); let recipient = recipient.to_string();
if !console::user_attended() { if !console::user_attended() {
eprintln!("Recipient: {}", recipient); eprintln!("Recipient: {}", recipient);
} }
println!("# created: {}", created); println!("{}", metadata);
println!("# recipient: {}", recipient); println!("# Recipient: {}", recipient);
println!("{}", stub.to_string()); println!("{}", stub.to_string());
} }