Print message if YubiKey is waiting for touch
Closes str4d/age-plugin-yubikey#44.
This commit is contained in:
+57
-21
@@ -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
@@ -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
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user