Skip to content

Commit

Permalink
Merge pull request #35 from str4d/ux-improvements
Browse files Browse the repository at this point in the history
UX improvements
  • Loading branch information
str4d authored Aug 20, 2021
2 parents 0bbea83 + f5f140d commit 6042d52
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 33 deletions.
2 changes: 1 addition & 1 deletion src/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ impl RecipientLine {
let epk = esk.public_key();
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![];
salt.extend_from_slice(epk_bytes.as_bytes());
Expand Down
2 changes: 1 addition & 1 deletion src/p256.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ impl Recipient {
/// This accepts both compressed (as used by the plugin) and uncompressed (as used in
/// the YubiKey certificate) encodings.
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.
Expand Down
17 changes: 11 additions & 6 deletions src/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ impl RecipientPluginV1 for RecipientPlugin {
bytes: &[u8],
) -> Result<(), recipient::Error> {
if let Some(pk) = if plugin_name == PLUGIN_NAME {
Recipient::from_bytes(&bytes)
Recipient::from_bytes(bytes)
} else {
None
} {
Expand All @@ -44,7 +44,7 @@ impl RecipientPluginV1 for RecipientPlugin {
bytes: &[u8],
) -> Result<(), recipient::Error> {
if let Some(stub) = if plugin_name == PLUGIN_NAME {
yubikey::Stub::from_bytes(&bytes, index)
yubikey::Stub::from_bytes(bytes, index)
} else {
None
} {
Expand Down Expand Up @@ -88,7 +88,7 @@ impl RecipientPluginV1 for RecipientPlugin {
self.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())
Expand All @@ -111,7 +111,7 @@ impl IdentityPluginV1 for IdentityPlugin {
bytes: &[u8],
) -> Result<(), identity::Error> {
if let Some(stub) = if plugin_name == PLUGIN_NAME {
yubikey::Stub::from_bytes(&bytes, index)
yubikey::Stub::from_bytes(bytes, index)
} else {
None
} {
Expand Down Expand Up @@ -145,7 +145,7 @@ impl IdentityPluginV1 for IdentityPlugin {
for (file, stanzas) in files.iter().enumerate() {
for (stanza_index, stanza) in stanzas.iter().enumerate() {
match (
format::RecipientLine::from_stanza(&stanza).map(|res| {
format::RecipientLine::from_stanza(stanza).map(|res| {
res.map_err(|_| identity::Error::Stanza {
file_index: file,
stanza_index,
Expand Down Expand Up @@ -213,14 +213,19 @@ 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 {
if file_keys.contains_key(&file_index) {
// We decrypted this file with an earlier YubiKey.
continue;
}

for (stanza_index, line) in stanzas.iter().enumerate() {
match conn.unwrap_file_key(&line) {
match conn.unwrap_file_key(line) {
Ok(file_key) => {
// We've managed to decrypt this file!
file_keys.entry(file_index).or_insert(Ok(file_key));
Expand Down
4 changes: 2 additions & 2 deletions src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ pub(crate) struct Metadata {
slot: RetiredSlotId,
name: String,
created: String,
pin_policy: Option<PinPolicy>,
pub(crate) pin_policy: Option<PinPolicy>,
touch_policy: Option<TouchPolicy>,
}

Expand Down Expand Up @@ -138,7 +138,7 @@ impl Metadata {
extract_name(cert, all)
.map(|(name, ours)| {
if ours {
let (pin_policy, touch_policy) = policies(&cert);
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
Expand Down
81 changes: 58 additions & 23 deletions src/yubikey.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -27,6 +28,7 @@ use crate::{
error::Error,
format::{RecipientLine, STANZA_KEY_LABEL},
p256::{Recipient, TAG_BYTES},
util::Metadata,
IDENTITY_PREFIX,
};

Expand Down Expand Up @@ -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,
Expand All @@ -316,46 +318,79 @@ 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 {
yubikey,
cert,
pk,
slot: self.slot,
tag: self.tag,
identity_index: self.identity_index,
}))
}
}

pub(crate) struct Connection {
yubikey: YubiKey,
cert: Certificate,
pk: Recipient,
slot: RetiredSlotId,
tag: [u8; 4],
identity_index: usize,
}

impl Connection {
pub(crate) fn recipient(&self) -> &Recipient {
&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, ()> {
assert_eq!(self.tag, line.tag);

Expand Down

0 comments on commit 6042d52

Please sign in to comment.