From 2c90195f99fb8441b6aad67df6e43040fb44c263 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Fri, 20 Aug 2021 15:11:39 +0100 Subject: [PATCH] Check PIN policy before requesting PIN Closes str4d/age-plugin-yubikey#34. --- src/plugin.rs | 2 +- src/util.rs | 2 +- src/yubikey.rs | 35 ++++++++++++++++++++++++++++++----- 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/plugin.rs b/src/plugin.rs index 5812ce1..9a917b9 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -213,7 +213,7 @@ impl IdentityPluginV1 for IdentityPlugin { } }; - if let Err(e) = conn.request_pin(&mut callbacks)? { + if let Err(e) = conn.request_pin_if_necessary(&mut callbacks)? { callbacks.error(e)?.unwrap(); continue; } diff --git a/src/util.rs b/src/util.rs index 4ec7c4b..bbf90e7 100644 --- a/src/util.rs +++ b/src/util.rs @@ -96,7 +96,7 @@ pub(crate) struct Metadata { slot: RetiredSlotId, name: String, created: String, - pin_policy: Option, + pub(crate) pin_policy: Option, touch_policy: Option, } diff --git a/src/yubikey.rs b/src/yubikey.rs index 53a5e2e..5b5ab36 100644 --- a/src/yubikey.rs +++ b/src/yubikey.rs @@ -18,6 +18,7 @@ use std::time::{Duration, SystemTime}; use yubikey_piv::{ certificate::{Certificate, PublicKeyInfo}, key::{decrypt_data, AlgorithmId, RetiredSlotId, SlotId}, + policy::PinPolicy, readers::Reader, yubikey::Serial, MgmKey, Readers, YubiKey, @@ -27,6 +28,7 @@ use crate::{ error::Error, format::{RecipientLine, STANZA_KEY_LABEL}, p256::{Recipient, TAG_BYTES}, + util::Metadata, IDENTITY_PREFIX, }; @@ -299,12 +301,12 @@ impl Stub { }; // Read the pubkey from the YubiKey slot and check it still matches. - let pk = match Certificate::read(&mut yubikey, SlotId::Retired(self.slot)) + let (cert, pk) = match Certificate::read(&mut yubikey, SlotId::Retired(self.slot)) .ok() .and_then(|cert| match cert.subject_pki() { - PublicKeyInfo::EcP256(pubkey) => { - Recipient::from_encoded(pubkey).filter(|pk| pk.tag() == self.tag) - } + PublicKeyInfo::EcP256(pubkey) => Recipient::from_encoded(pubkey) + .filter(|pk| pk.tag() == self.tag) + .map(|pk| (cert, pk)), _ => None, }) { Some(pk) => pk, @@ -318,6 +320,7 @@ impl Stub { Ok(Ok(Connection { yubikey, + cert, pk, slot: self.slot, tag: self.tag, @@ -328,6 +331,7 @@ impl Stub { pub(crate) struct Connection { yubikey: YubiKey, + cert: Certificate, pk: Recipient, slot: RetiredSlotId, tag: [u8; 4], @@ -339,10 +343,31 @@ impl Connection { &self.pk } - pub(crate) fn request_pin( + pub(crate) fn request_pin_if_necessary( &mut self, callbacks: &mut dyn Callbacks, ) -> io::Result> { + // Check if we can skip requesting a PIN. + let (_, cert) = x509_parser::parse_x509_certificate(self.cert.as_ref()).unwrap(); + match Metadata::extract(&mut self.yubikey, self.slot, &cert, true) { + Some(metadata) => { + if let Some(PinPolicy::Never) = metadata.pin_policy { + return Ok(Ok(())); + } + } + None => { + return Ok(Err(identity::Error::Identity { + index: self.identity_index, + message: format!( + "Certificate for YubiKey identity contains an invalid PIN policy" + ), + })) + } + } + + // The policy requires a PIN, so request it. + // Note that we can't distinguish between PinPolicy::Once and PinPolicy::Always + // because this plugin is ephemeral, so we always request the PIN. let pin = match callbacks.request_secret(&format!( "Enter PIN for YubiKey with serial {}", self.yubikey.serial()