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:
+4
-4
@@ -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
@@ -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
@@ -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());
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user