Disconnect without resetting YubiKeys if it is safe to do so

This enables the PIN caches to be preserved across age-plugin-yubikey
processes, allowing PIN policies of "once" to become meaningful.
This commit is contained in:
Jack Grigg
2023-01-02 16:11:38 +00:00
parent 87541510ad
commit 9418921dab
5 changed files with 48 additions and 4 deletions
Generated
+1 -2
View File
@@ -2122,8 +2122,7 @@ dependencies = [
[[package]] [[package]]
name = "yubikey" name = "yubikey"
version = "0.7.0" version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://github.com/iqlusioninc/yubikey.rs.git?rev=1d33ea174791f699dfc0e6a06648aa3d9e066144#1d33ea174791f699dfc0e6a06648aa3d9e066144"
checksum = "10e6fa9476951a9b93d9a31aa5554b5bbac7aafdc5b23e663eb3f9b635c86053"
dependencies = [ dependencies = [
"base16ct", "base16ct",
"chrono", "chrono",
+3
View File
@@ -53,3 +53,6 @@ sysinfo = "0.27"
[dev-dependencies] [dev-dependencies]
flate2 = "1" flate2 = "1"
man = "0.3" man = "0.3"
[patch.crates-io]
yubikey = { git = "https://github.com/iqlusioninc/yubikey.rs.git", rev = "1d33ea174791f699dfc0e6a06648aa3d9e066144" }
+24
View File
@@ -236,6 +236,23 @@ pub(crate) fn open(serial: Option<Serial>) -> Result<YubiKey, Error> {
Ok(yubikey) Ok(yubikey)
} }
/// Disconnect from the YubiKey without resetting it.
///
/// This can be used to preserve the YubiKey's PIN and touch caches. There are two cases
/// where we want to do this:
///
/// - We connected to this YubiKey in a read-only context, so we have not made any changes
/// to the YubiKey's state. However, we might have asked an agent to release the YubiKey
/// in `key::open_connection`, and we want to allow any state it may have left behind
/// (such as cached PINs or touches) to persist beyond our execution, for usability.
/// - We opened this connection in a decryption context, so the only changes to the
/// YubiKey's state were to potentially cache the PIN and/or touch (depending on the
/// policies of the slot). We want to allow these to persist beyond our execution, for
/// usability.
pub(crate) fn disconnect_without_reset(yubikey: YubiKey) {
let _ = yubikey.disconnect(pcsc::Disposition::LeaveCard);
}
pub(crate) fn manage(yubikey: &mut YubiKey) -> Result<(), Error> { pub(crate) fn manage(yubikey: &mut YubiKey) -> Result<(), Error> {
const DEFAULT_PIN: &str = "123456"; const DEFAULT_PIN: &str = "123456";
const DEFAULT_PUK: &str = "12345678"; const DEFAULT_PUK: &str = "12345678";
@@ -674,6 +691,13 @@ impl Connection {
Err(_) => Err(()), Err(_) => Err(()),
} }
} }
/// Close this connection without resetting the YubiKey.
///
/// This can be used to preserve the YubiKey's PIN and touch caches.
pub(crate) fn disconnect_without_reset(self) {
disconnect_without_reset(self.yubikey);
}
} }
#[cfg(test)] #[cfg(test)]
+18 -2
View File
@@ -181,6 +181,13 @@ fn generate(flags: PluginFlags) -> Result<(), Error> {
util::print_identity(stub, recipient, metadata); util::print_identity(stub, recipient, metadata);
// We have written to the YubiKey, which means we've authenticated with the management
// key. Out of an abundance of caution, we let the YubiKey be reset on disconnect,
// which will clear its PIN and touch caches. This has as small negative UX effect,
// but identity generation is a relatively infrequent occurrence, and users are more
// likely to see their cached PINs reset due to switching applets (e.g. from PIV to
// FIDO2).
Ok(()) Ok(())
} }
@@ -200,6 +207,8 @@ fn print_single(
printer(stub, recipient, metadata); printer(stub, recipient, metadata);
key::disconnect_without_reset(yubikey);
Ok(()) Ok(())
} }
@@ -233,6 +242,8 @@ fn print_multiple(
println!(); println!();
} }
println!(); println!();
key::disconnect_without_reset(yubikey);
} }
if printed > 1 { if printed > 1 {
eprintln!("{}", fl!("printed-multiple", kind = kind, count = printed)); eprintln!("{}", fl!("printed-multiple", kind = kind, count = printed));
@@ -360,11 +371,13 @@ fn main() -> Result<(), Error> {
.iter() .iter()
.map(|reader| { .map(|reader| {
key::open_connection(reader).map(|yk| { key::open_connection(reader).map(|yk| {
fl!( let name = fl!(
"cli-setup-yk-name", "cli-setup-yk-name",
yubikey_name = reader.name(), yubikey_name = reader.name(),
yubikey_serial = yk.serial().to_string(), yubikey_serial = yk.serial().to_string(),
) );
key::disconnect_without_reset(yk);
name
}) })
}) })
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
@@ -457,8 +470,10 @@ fn main() -> Result<(), Error> {
util::Metadata::extract(&mut yubikey, slot, key.certificate(), true) util::Metadata::extract(&mut yubikey, slot, key.certificate(), true)
.unwrap(); .unwrap();
key::disconnect_without_reset(yubikey);
((stub, recipient, metadata), false) ((stub, recipient, metadata), false)
} else { } else {
key::disconnect_without_reset(yubikey);
return Ok(()); return Ok(());
} }
} else { } else {
@@ -540,6 +555,7 @@ fn main() -> Result<(), Error> {
true, true,
) )
} else { } else {
key::disconnect_without_reset(yubikey);
return Ok(()); return Ok(());
} }
} }
+2
View File
@@ -249,6 +249,8 @@ impl IdentityPluginV1 for IdentityPlugin {
} }
} }
} }
conn.disconnect_without_reset();
} }
Ok(file_keys) Ok(file_keys)
} }