Merge pull request #45 from str4d/44-request-touch

Print message if YubiKey is waiting for touch
This commit is contained in:
str4d
2021-12-20 03:23:49 +00:00
committed by GitHub
3 changed files with 59 additions and 23 deletions
+57 -21
View File
@@ -14,12 +14,12 @@ use std::fmt;
use std::io; use std::io;
use std::iter; use std::iter;
use std::thread::sleep; use std::thread::sleep;
use std::time::{Duration, SystemTime}; use std::time::{Duration, Instant, SystemTime};
use yubikey::{ use yubikey::{
certificate::{Certificate, PublicKeyInfo}, certificate::{Certificate, PublicKeyInfo},
piv::{decrypt_data, AlgorithmId, RetiredSlotId, SlotId}, piv::{decrypt_data, AlgorithmId, RetiredSlotId, SlotId},
reader::{Context, Reader}, reader::{Context, Reader},
MgmKey, PinPolicy, Serial, YubiKey, MgmKey, PinPolicy, Serial, TouchPolicy, YubiKey,
}; };
use crate::{ use crate::{
@@ -323,6 +323,8 @@ impl Stub {
slot: self.slot, slot: self.slot,
tag: self.tag, tag: self.tag,
identity_index: self.identity_index, identity_index: self.identity_index,
cached_metadata: None,
last_touch: None,
})) }))
} }
} }
@@ -334,6 +336,8 @@ pub(crate) struct Connection {
slot: RetiredSlotId, slot: RetiredSlotId,
tag: [u8; 4], tag: [u8; 4],
identity_index: usize, identity_index: usize,
cached_metadata: Option<Metadata>,
last_touch: Option<Instant>,
} }
impl Connection { impl Connection {
@@ -346,20 +350,23 @@ impl Connection {
callbacks: &mut dyn Callbacks<E>, callbacks: &mut dyn Callbacks<E>,
) -> io::Result<Result<(), identity::Error>> { ) -> io::Result<Result<(), identity::Error>> {
// Check if we can skip requesting a PIN. // Check if we can skip requesting a PIN.
let (_, cert) = x509_parser::parse_x509_certificate(self.cert.as_ref()).unwrap(); if self.cached_metadata.is_none() {
match Metadata::extract(&mut self.yubikey, self.slot, &cert, true) { let (_, cert) = x509_parser::parse_x509_certificate(self.cert.as_ref()).unwrap();
Some(metadata) => { self.cached_metadata =
if let Some(PinPolicy::Never) = metadata.pin_policy { match Metadata::extract(&mut self.yubikey, self.slot, &cert, true) {
return Ok(Ok(())); None => {
} return Ok(Err(identity::Error::Identity {
} index: self.identity_index,
None => { message:
return Ok(Err(identity::Error::Identity { "Certificate for YubiKey identity contains an invalid PIN policy"
index: self.identity_index, .to_string(),
message: "Certificate for YubiKey identity contains an invalid PIN policy" }))
.to_string(), }
})) metadata => metadata,
} };
}
if let Some(PinPolicy::Never) = self.cached_metadata.as_ref().and_then(|m| m.pin_policy) {
return Ok(Ok(()));
} }
// The policy requires a PIN, so request it. // The policy requires a PIN, so request it.
@@ -389,9 +396,29 @@ impl Connection {
Ok(Ok(())) Ok(Ok(()))
} }
pub(crate) fn unwrap_file_key(&mut self, line: &RecipientLine) -> Result<FileKey, ()> { pub(crate) fn unwrap_file_key<E>(
&mut self,
line: &RecipientLine,
callbacks: &mut dyn Callbacks<E>,
) -> io::Result<Result<FileKey, ()>> {
assert_eq!(self.tag, line.tag); assert_eq!(self.tag, line.tag);
// If the touch policy requires it, request a touch.
let requested_touch = match (
self.cached_metadata.as_ref().and_then(|m| m.touch_policy),
self.last_touch,
) {
(Some(TouchPolicy::Always), _) | (Some(TouchPolicy::Cached), None) => {
callbacks.message("👆 Please touch the YubiKey")?.unwrap();
true
}
(Some(TouchPolicy::Cached), Some(last)) if last.elapsed() >= FIFTEEN_SECONDS => {
callbacks.message("👆 Please touch the YubiKey")?.unwrap();
true
}
_ => false,
};
// The YubiKey API for performing scalar multiplication takes the point in its // The YubiKey API for performing scalar multiplication takes the point in its
// uncompressed SEC-1 encoding. // uncompressed SEC-1 encoding.
let shared_secret = match decrypt_data( let shared_secret = match decrypt_data(
@@ -401,9 +428,18 @@ impl Connection {
SlotId::Retired(self.slot), SlotId::Retired(self.slot),
) { ) {
Ok(res) => res, Ok(res) => res,
Err(_) => return Err(()), Err(_) => return Ok(Err(())),
}; };
// If we requested a touch and reached here, the user touched the YubiKey.
if requested_touch {
if let Some(TouchPolicy::Cached) =
self.cached_metadata.as_ref().and_then(|m| m.touch_policy)
{
self.last_touch = Some(Instant::now());
}
}
let mut salt = vec![]; let mut salt = vec![];
salt.extend_from_slice(line.epk_bytes.as_bytes()); salt.extend_from_slice(line.epk_bytes.as_bytes());
salt.extend_from_slice(self.pk.to_encoded().as_bytes()); salt.extend_from_slice(self.pk.to_encoded().as_bytes());
@@ -413,10 +449,10 @@ impl Connection {
// A failure to decrypt is fatal, because we assume that we won't // A failure to decrypt is fatal, because we assume that we won't
// encounter 32-bit collisions on the key tag embedded in the header. // encounter 32-bit collisions on the key tag embedded in the header.
match aead_decrypt(&enc_key, FILE_KEY_BYTES, &line.encrypted_file_key) { match aead_decrypt(&enc_key, FILE_KEY_BYTES, &line.encrypted_file_key) {
Ok(pt) => Ok(TryInto::<[u8; FILE_KEY_BYTES]>::try_into(&pt[..]) Ok(pt) => Ok(Ok(TryInto::<[u8; FILE_KEY_BYTES]>::try_into(&pt[..])
.unwrap() .unwrap()
.into()), .into())),
Err(_) => Err(()), Err(_) => Ok(Err(())),
} }
} }
} }
+1 -1
View File
@@ -222,7 +222,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, &mut callbacks)? {
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));
+1 -1
View File
@@ -96,7 +96,7 @@ pub(crate) struct Metadata {
name: String, name: String,
created: String, created: String,
pub(crate) pin_policy: Option<PinPolicy>, pub(crate) pin_policy: Option<PinPolicy>,
touch_policy: Option<TouchPolicy>, pub(crate) touch_policy: Option<TouchPolicy>,
} }
impl Metadata { impl Metadata {