Merge pull request #35 from str4d/ux-improvements

UX improvements
This commit is contained in:
str4d
2021-08-20 16:30:46 +01:00
committed by GitHub
5 changed files with 73 additions and 33 deletions
+1 -1
View File
@@ -106,7 +106,7 @@ impl RecipientLine {
let epk = esk.public_key(); let epk = esk.public_key();
let epk_bytes = EphemeralKeyBytes::from_public_key(&epk); let epk_bytes = EphemeralKeyBytes::from_public_key(&epk);
let shared_secret = esk.diffie_hellman(&pk.public_key()); let shared_secret = esk.diffie_hellman(pk.public_key());
let mut salt = vec![]; let mut salt = vec![];
salt.extend_from_slice(epk_bytes.as_bytes()); salt.extend_from_slice(epk_bytes.as_bytes());
+1 -1
View File
@@ -48,7 +48,7 @@ impl Recipient {
/// This accepts both compressed (as used by the plugin) and uncompressed (as used in /// This accepts both compressed (as used by the plugin) and uncompressed (as used in
/// the YubiKey certificate) encodings. /// the YubiKey certificate) encodings.
pub(crate) fn from_encoded(encoded: &p256::EncodedPoint) -> Option<Self> { pub(crate) fn from_encoded(encoded: &p256::EncodedPoint) -> Option<Self> {
p256::PublicKey::from_encoded_point(&encoded).map(Recipient) p256::PublicKey::from_encoded_point(encoded).map(Recipient)
} }
/// Returns the compressed SEC-1 encoding of this recipient. /// Returns the compressed SEC-1 encoding of this recipient.
+11 -6
View File
@@ -23,7 +23,7 @@ impl RecipientPluginV1 for RecipientPlugin {
bytes: &[u8], bytes: &[u8],
) -> Result<(), recipient::Error> { ) -> Result<(), recipient::Error> {
if let Some(pk) = if plugin_name == PLUGIN_NAME { if let Some(pk) = if plugin_name == PLUGIN_NAME {
Recipient::from_bytes(&bytes) Recipient::from_bytes(bytes)
} else { } else {
None None
} { } {
@@ -44,7 +44,7 @@ impl RecipientPluginV1 for RecipientPlugin {
bytes: &[u8], bytes: &[u8],
) -> Result<(), recipient::Error> { ) -> Result<(), recipient::Error> {
if let Some(stub) = if plugin_name == PLUGIN_NAME { if let Some(stub) = if plugin_name == PLUGIN_NAME {
yubikey::Stub::from_bytes(&bytes, index) yubikey::Stub::from_bytes(bytes, index)
} else { } else {
None None
} { } {
@@ -88,7 +88,7 @@ impl RecipientPluginV1 for RecipientPlugin {
self.recipients self.recipients
.iter() .iter()
.chain(yk_recipients.iter()) .chain(yk_recipients.iter())
.map(|pk| format::RecipientLine::wrap_file_key(&file_key, &pk).into()) .map(|pk| format::RecipientLine::wrap_file_key(&file_key, pk).into())
.collect() .collect()
}) })
.collect()) .collect())
@@ -111,7 +111,7 @@ impl IdentityPluginV1 for IdentityPlugin {
bytes: &[u8], bytes: &[u8],
) -> Result<(), identity::Error> { ) -> Result<(), identity::Error> {
if let Some(stub) = if plugin_name == PLUGIN_NAME { if let Some(stub) = if plugin_name == PLUGIN_NAME {
yubikey::Stub::from_bytes(&bytes, index) yubikey::Stub::from_bytes(bytes, index)
} else { } else {
None None
} { } {
@@ -145,7 +145,7 @@ impl IdentityPluginV1 for IdentityPlugin {
for (file, stanzas) in files.iter().enumerate() { for (file, stanzas) in files.iter().enumerate() {
for (stanza_index, stanza) in stanzas.iter().enumerate() { for (stanza_index, stanza) in stanzas.iter().enumerate() {
match ( match (
format::RecipientLine::from_stanza(&stanza).map(|res| { format::RecipientLine::from_stanza(stanza).map(|res| {
res.map_err(|_| identity::Error::Stanza { res.map_err(|_| identity::Error::Stanza {
file_index: file, file_index: file,
stanza_index, stanza_index,
@@ -213,6 +213,11 @@ impl IdentityPluginV1 for IdentityPlugin {
} }
}; };
if let Err(e) = conn.request_pin_if_necessary(&mut callbacks)? {
callbacks.error(e)?.unwrap();
continue;
}
for (&file_index, stanzas) in files { for (&file_index, stanzas) in files {
if file_keys.contains_key(&file_index) { if file_keys.contains_key(&file_index) {
// We decrypted this file with an earlier YubiKey. // We decrypted this file with an earlier YubiKey.
@@ -220,7 +225,7 @@ impl IdentityPluginV1 for IdentityPlugin {
} }
for (stanza_index, line) in stanzas.iter().enumerate() { for (stanza_index, line) in stanzas.iter().enumerate() {
match conn.unwrap_file_key(&line) { match conn.unwrap_file_key(line) {
Ok(file_key) => { Ok(file_key) => {
// We've managed to decrypt this file! // We've managed to decrypt this file!
file_keys.entry(file_index).or_insert(Ok(file_key)); file_keys.entry(file_index).or_insert(Ok(file_key));
+2 -2
View File
@@ -96,7 +96,7 @@ pub(crate) struct Metadata {
slot: RetiredSlotId, slot: RetiredSlotId,
name: String, name: String,
created: String, created: String,
pin_policy: Option<PinPolicy>, pub(crate) pin_policy: Option<PinPolicy>,
touch_policy: Option<TouchPolicy>, touch_policy: Option<TouchPolicy>,
} }
@@ -138,7 +138,7 @@ impl Metadata {
extract_name(cert, all) extract_name(cert, all)
.map(|(name, ours)| { .map(|(name, ours)| {
if ours { if ours {
let (pin_policy, touch_policy) = policies(&cert); let (pin_policy, touch_policy) = policies(cert);
(name, pin_policy, touch_policy) (name, pin_policy, touch_policy)
} else { } else {
// We can extract the PIN and touch policies via an attestation. This // We can extract the PIN and touch policies via an attestation. This
+58 -23
View File
@@ -18,6 +18,7 @@ use std::time::{Duration, SystemTime};
use yubikey_piv::{ use yubikey_piv::{
certificate::{Certificate, PublicKeyInfo}, certificate::{Certificate, PublicKeyInfo},
key::{decrypt_data, AlgorithmId, RetiredSlotId, SlotId}, key::{decrypt_data, AlgorithmId, RetiredSlotId, SlotId},
policy::PinPolicy,
readers::Reader, readers::Reader,
yubikey::Serial, yubikey::Serial,
MgmKey, Readers, YubiKey, MgmKey, Readers, YubiKey,
@@ -27,6 +28,7 @@ use crate::{
error::Error, error::Error,
format::{RecipientLine, STANZA_KEY_LABEL}, format::{RecipientLine, STANZA_KEY_LABEL},
p256::{Recipient, TAG_BYTES}, p256::{Recipient, TAG_BYTES},
util::Metadata,
IDENTITY_PREFIX, IDENTITY_PREFIX,
}; };
@@ -299,12 +301,12 @@ impl Stub {
}; };
// Read the pubkey from the YubiKey slot and check it still matches. // 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() .ok()
.and_then(|cert| match cert.subject_pki() { .and_then(|cert| match cert.subject_pki() {
PublicKeyInfo::EcP256(pubkey) => { PublicKeyInfo::EcP256(pubkey) => Recipient::from_encoded(pubkey)
Recipient::from_encoded(pubkey).filter(|pk| pk.tag() == self.tag) .filter(|pk| pk.tag() == self.tag)
} .map(|pk| (cert, pk)),
_ => None, _ => None,
}) { }) {
Some(pk) => pk, Some(pk) => pk,
@@ -316,39 +318,24 @@ impl Stub {
} }
}; };
let pin = match callbacks.request_secret(&format!(
"Enter PIN for YubiKey with serial {}",
self.serial
))? {
Ok(pin) => pin,
Err(_) => {
return Ok(Err(identity::Error::Identity {
index: self.identity_index,
message: format!("A PIN is required for YubiKey with serial {}", self.serial),
}))
}
};
if yubikey.verify_pin(pin.expose_secret().as_bytes()).is_err() {
return Ok(Err(identity::Error::Identity {
index: self.identity_index,
message: "Invalid YubiKey PIN".to_owned(),
}));
}
Ok(Ok(Connection { Ok(Ok(Connection {
yubikey, yubikey,
cert,
pk, pk,
slot: self.slot, slot: self.slot,
tag: self.tag, tag: self.tag,
identity_index: self.identity_index,
})) }))
} }
} }
pub(crate) struct Connection { pub(crate) struct Connection {
yubikey: YubiKey, yubikey: YubiKey,
cert: Certificate,
pk: Recipient, pk: Recipient,
slot: RetiredSlotId, slot: RetiredSlotId,
tag: [u8; 4], tag: [u8; 4],
identity_index: usize,
} }
impl Connection { impl Connection {
@@ -356,6 +343,54 @@ impl Connection {
&self.pk &self.pk
} }
pub(crate) fn request_pin_if_necessary<E>(
&mut self,
callbacks: &mut dyn Callbacks<E>,
) -> io::Result<Result<(), identity::Error>> {
// 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: "Certificate for YubiKey identity contains an invalid PIN policy"
.to_string(),
}))
}
}
// 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()
))? {
Ok(pin) => pin,
Err(_) => {
return Ok(Err(identity::Error::Identity {
index: self.identity_index,
message: format!(
"A PIN is required for YubiKey with serial {}",
self.yubikey.serial()
),
}))
}
};
if let Err(e) = self.yubikey.verify_pin(pin.expose_secret().as_bytes()) {
return Ok(Err(identity::Error::Identity {
index: self.identity_index,
message: format!("{:?}", Error::YubiKey(e)),
}));
}
Ok(Ok(()))
}
pub(crate) fn unwrap_file_key(&mut self, line: &RecipientLine) -> Result<FileKey, ()> { pub(crate) fn unwrap_file_key(&mut self, line: &RecipientLine) -> Result<FileKey, ()> {
assert_eq!(self.tag, line.tag); assert_eq!(self.tag, line.tag);