Merge branch 'version-0.3.3' back into main
This commit is contained in:
@@ -24,6 +24,16 @@ to 0.3.0 are beta releases.
|
|||||||
- Identities can now be generated with a PIN policy of "always" (in previous
|
- Identities can now be generated with a PIN policy of "always" (in previous
|
||||||
versions of `age-plugin-yubikey` this would cause an error).
|
versions of `age-plugin-yubikey` this would cause an error).
|
||||||
|
|
||||||
|
## [0.3.3] - 2023-02-11
|
||||||
|
### Fixed
|
||||||
|
- When `age-plugin-yubikey` assists the user in changing their PIN from the
|
||||||
|
default PIN, it no longer tells the user that PINs shorter than 6 characters
|
||||||
|
are allowed, and instead loops until the user enters a PIN of valid length.
|
||||||
|
It also now prevents the user from setting their PIN to the default PIN, to
|
||||||
|
avoid creating a cycle.
|
||||||
|
- More kinds of SmartCard readers are ignored when they have no SmartCard
|
||||||
|
inserted.
|
||||||
|
|
||||||
## [0.3.2] - 2023-01-01
|
## [0.3.2] - 2023-01-01
|
||||||
### Changed
|
### Changed
|
||||||
- The "sharing violation" logic now also sends SIGHUP to any `yubikey-agent`
|
- The "sharing violation" logic now also sends SIGHUP to any `yubikey-agent`
|
||||||
|
|||||||
Generated
+1
-1
@@ -50,7 +50,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "age-plugin-yubikey"
|
name = "age-plugin-yubikey"
|
||||||
version = "0.3.2"
|
version = "0.3.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"age-core",
|
"age-core",
|
||||||
"age-plugin",
|
"age-plugin",
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "age-plugin-yubikey"
|
name = "age-plugin-yubikey"
|
||||||
description = "YubiKey plugin for age clients"
|
description = "YubiKey plugin for age clients"
|
||||||
version = "0.3.2"
|
version = "0.3.3"
|
||||||
authors = ["Jack Grigg <thestr4d@gmail.com>"]
|
authors = ["Jack Grigg <thestr4d@gmail.com>"]
|
||||||
repository = "https://github.com/str4d/age-plugin-yubikey"
|
repository = "https://github.com/str4d/age-plugin-yubikey"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|||||||
@@ -127,13 +127,15 @@ mgr-change-default-pin =
|
|||||||
✨ Your {-yubikey} is using the default PIN. Let's change it!
|
✨ Your {-yubikey} is using the default PIN. Let's change it!
|
||||||
✨ We'll also set the PUK equal to the PIN.
|
✨ We'll also set the PUK equal to the PIN.
|
||||||
|
|
||||||
🔐 The PIN is up to 8 numbers, letters, or symbols. Not just numbers!
|
🔐 The PIN can be numbers, letters, or symbols. Not just numbers!
|
||||||
|
📏 The PIN must be at least 6 and at most 8 characters in length.
|
||||||
❌ Your keys will be lost if the PIN and PUK are locked after 3 incorrect tries.
|
❌ Your keys will be lost if the PIN and PUK are locked after 3 incorrect tries.
|
||||||
|
|
||||||
mgr-enter-current-puk = Enter current PUK (default is {$default_puk})
|
mgr-enter-current-puk = Enter current PUK (default is {$default_puk})
|
||||||
mgr-choose-new-pin = Choose a new PIN/PUK
|
mgr-choose-new-pin = Choose a new PIN/PUK
|
||||||
mgr-repeat-new-pin = Repeat the PIN/PUK
|
mgr-repeat-new-pin = Repeat the PIN/PUK
|
||||||
mgr-pin-mismatch = PINs don't match
|
mgr-pin-mismatch = PINs don't match
|
||||||
|
mgr-nope-default-pin = You entered the default PIN again. You need to change it.
|
||||||
|
|
||||||
mgr-changing-mgmt-key =
|
mgr-changing-mgmt-key =
|
||||||
✨ Your {-yubikey} is using the default management key.
|
✨ Your {-yubikey} is using the default management key.
|
||||||
@@ -186,7 +188,6 @@ rec-custom-mgmt-key =
|
|||||||
|
|
||||||
err-invalid-flag-command = Flag '{$flag}' cannot be used with '{$command}'.
|
err-invalid-flag-command = Flag '{$flag}' cannot be used with '{$command}'.
|
||||||
err-invalid-flag-tui = Flag '{$flag}' cannot be used with the interactive interface.
|
err-invalid-flag-tui = Flag '{$flag}' cannot be used with the interactive interface.
|
||||||
err-invalid-pin-length = The PIN needs to be 1-8 characters.
|
|
||||||
err-invalid-pin-policy = Invalid PIN policy '{$policy}' (expected [{$expected}]).
|
err-invalid-pin-policy = Invalid PIN policy '{$policy}' (expected [{$expected}]).
|
||||||
err-invalid-slot = Invalid slot '{$slot}' (expected number between 1 and 20).
|
err-invalid-slot = Invalid slot '{$slot}' (expected number between 1 and 20).
|
||||||
err-invalid-touch-policy = Invalid touch policy '{$policy}' (expected [{$expected}]).
|
err-invalid-touch-policy = Invalid touch policy '{$policy}' (expected [{$expected}]).
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ pub enum Error {
|
|||||||
CustomManagementKey,
|
CustomManagementKey,
|
||||||
InvalidFlagCommand(String, String),
|
InvalidFlagCommand(String, String),
|
||||||
InvalidFlagTui(String),
|
InvalidFlagTui(String),
|
||||||
InvalidPinLength,
|
|
||||||
InvalidPinPolicy(String),
|
InvalidPinPolicy(String),
|
||||||
InvalidSlot(u8),
|
InvalidSlot(u8),
|
||||||
InvalidTouchPolicy(String),
|
InvalidTouchPolicy(String),
|
||||||
@@ -63,7 +62,6 @@ impl fmt::Debug for Error {
|
|||||||
command = command.as_str(),
|
command = command.as_str(),
|
||||||
)?,
|
)?,
|
||||||
Error::InvalidFlagTui(flag) => wlnfl!(f, "err-invalid-flag-tui", flag = flag.as_str())?,
|
Error::InvalidFlagTui(flag) => wlnfl!(f, "err-invalid-flag-tui", flag = flag.as_str())?,
|
||||||
Error::InvalidPinLength => wlnfl!(f, "err-invalid-pin-length")?,
|
|
||||||
Error::InvalidPinPolicy(s) => wlnfl!(
|
Error::InvalidPinPolicy(s) => wlnfl!(
|
||||||
f,
|
f,
|
||||||
"err-invalid-pin-policy",
|
"err-invalid-pin-policy",
|
||||||
|
|||||||
+74
-42
@@ -3,12 +3,13 @@
|
|||||||
use age_core::{
|
use age_core::{
|
||||||
format::{FileKey, FILE_KEY_BYTES},
|
format::{FileKey, FILE_KEY_BYTES},
|
||||||
primitives::{aead_decrypt, hkdf},
|
primitives::{aead_decrypt, hkdf},
|
||||||
secrecy::ExposeSecret,
|
secrecy::{ExposeSecret, SecretString},
|
||||||
};
|
};
|
||||||
use age_plugin::{identity, Callbacks};
|
use age_plugin::{identity, Callbacks};
|
||||||
use bech32::{ToBase32, Variant};
|
use bech32::{ToBase32, Variant};
|
||||||
use dialoguer::Password;
|
use dialoguer::Password;
|
||||||
use log::{debug, error, warn};
|
use log::{debug, error, warn};
|
||||||
|
use std::convert::Infallible;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::iter;
|
use std::iter;
|
||||||
@@ -40,7 +41,7 @@ pub(crate) fn is_connected(reader: Reader) -> bool {
|
|||||||
pub(crate) fn filter_connected(reader: &Reader) -> bool {
|
pub(crate) fn filter_connected(reader: &Reader) -> bool {
|
||||||
match reader.open() {
|
match reader.open() {
|
||||||
Err(yubikey::Error::PcscError {
|
Err(yubikey::Error::PcscError {
|
||||||
inner: Some(pcsc::Error::RemovedCard),
|
inner: Some(pcsc::Error::NoSmartcard | pcsc::Error::RemovedCard),
|
||||||
}) => {
|
}) => {
|
||||||
warn!(
|
warn!(
|
||||||
"{}",
|
"{}",
|
||||||
@@ -253,6 +254,31 @@ pub(crate) fn disconnect_without_reset(yubikey: YubiKey) {
|
|||||||
let _ = yubikey.disconnect(pcsc::Disposition::LeaveCard);
|
let _ = yubikey.disconnect(pcsc::Disposition::LeaveCard);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn request_pin<E>(
|
||||||
|
mut prompt: impl FnMut(Option<String>) -> io::Result<Result<SecretString, E>>,
|
||||||
|
serial: Serial,
|
||||||
|
) -> io::Result<Result<SecretString, E>> {
|
||||||
|
let mut prev_error = None;
|
||||||
|
loop {
|
||||||
|
prev_error = Some(match prompt(prev_error)? {
|
||||||
|
Ok(pin) => match pin.expose_secret().len() {
|
||||||
|
// A PIN must be between 6 and 8 characters.
|
||||||
|
6..=8 => break Ok(Ok(pin)),
|
||||||
|
// If the string is 44 bytes and starts with the YubiKey's serial
|
||||||
|
// encoded as 12-byte modhex, the user probably touched the YubiKey
|
||||||
|
// early and "typed" an OTP.
|
||||||
|
44 if pin.expose_secret().starts_with(&otp_serial_prefix(serial)) => {
|
||||||
|
fl!("plugin-err-accidental-touch")
|
||||||
|
}
|
||||||
|
// Otherwise, the PIN is either too short or too long.
|
||||||
|
0..=5 => fl!("plugin-err-pin-too-short"),
|
||||||
|
_ => fl!("plugin-err-pin-too-long"),
|
||||||
|
},
|
||||||
|
Err(e) => break Ok(Err(e)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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";
|
||||||
@@ -276,13 +302,28 @@ pub(crate) fn manage(yubikey: &mut YubiKey) -> Result<(), Error> {
|
|||||||
let current_puk = Password::new()
|
let current_puk = Password::new()
|
||||||
.with_prompt(fl!("mgr-enter-current-puk", default_puk = DEFAULT_PUK))
|
.with_prompt(fl!("mgr-enter-current-puk", default_puk = DEFAULT_PUK))
|
||||||
.interact()?;
|
.interact()?;
|
||||||
let new_pin = Password::new()
|
let new_pin = loop {
|
||||||
.with_prompt(fl!("mgr-choose-new-pin"))
|
let pin = request_pin(
|
||||||
.with_confirmation(fl!("mgr-repeat-new-pin"), fl!("mgr-pin-mismatch"))
|
|prev_error| {
|
||||||
.interact()?;
|
if let Some(err) = prev_error {
|
||||||
if new_pin.len() > 8 {
|
eprintln!("{}", err);
|
||||||
return Err(Error::InvalidPinLength);
|
}
|
||||||
}
|
Password::new()
|
||||||
|
.with_prompt(fl!("mgr-choose-new-pin"))
|
||||||
|
.with_confirmation(fl!("mgr-repeat-new-pin"), fl!("mgr-pin-mismatch"))
|
||||||
|
.interact()
|
||||||
|
.map(|pin| Result::<_, Infallible>::Ok(SecretString::new(pin)))
|
||||||
|
},
|
||||||
|
yubikey.serial(),
|
||||||
|
)?
|
||||||
|
.unwrap();
|
||||||
|
if pin.expose_secret() == DEFAULT_PIN {
|
||||||
|
eprintln!("{}", fl!("mgr-nope-default-pin"));
|
||||||
|
} else {
|
||||||
|
break pin;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let new_pin = new_pin.expose_secret();
|
||||||
yubikey.change_puk(current_puk.as_bytes(), new_pin.as_bytes())?;
|
yubikey.change_puk(current_puk.as_bytes(), new_pin.as_bytes())?;
|
||||||
yubikey.change_pin(pin.as_bytes(), new_pin.as_bytes())?;
|
yubikey.change_pin(pin.as_bytes(), new_pin.as_bytes())?;
|
||||||
}
|
}
|
||||||
@@ -599,39 +640,30 @@ impl Connection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The policy requires a PIN, so request it.
|
// The policy requires a PIN, so request it.
|
||||||
let enter_pin_msg = fl!(
|
let pin = match request_pin(
|
||||||
"plugin-enter-pin",
|
|prev_error| {
|
||||||
yubikey_serial = self.yubikey.serial().to_string(),
|
callbacks.request_secret(&format!(
|
||||||
);
|
"{}{}{}",
|
||||||
let mut message = enter_pin_msg.clone();
|
prev_error.as_deref().unwrap_or(""),
|
||||||
let pin = loop {
|
prev_error.as_deref().map(|_| " ").unwrap_or(""),
|
||||||
message = match callbacks.request_secret(&message)? {
|
fl!(
|
||||||
Ok(pin) => match pin.expose_secret().len() {
|
"plugin-enter-pin",
|
||||||
// A PIN must be between 6 and 8 characters.
|
yubikey_serial = self.yubikey.serial().to_string(),
|
||||||
6..=8 => break pin,
|
)
|
||||||
// If the string is 44 bytes and starts with the YubiKey's serial
|
))
|
||||||
// encoded as 12-byte modhex, the user probably touched the YubiKey
|
},
|
||||||
// early and "typed" an OTP.
|
self.yubikey.serial(),
|
||||||
44 if pin
|
)? {
|
||||||
.expose_secret()
|
Ok(pin) => pin,
|
||||||
.starts_with(&otp_serial_prefix(self.yubikey.serial())) =>
|
Err(_) => {
|
||||||
{
|
return Ok(Err(identity::Error::Identity {
|
||||||
format!("{} {}", fl!("plugin-err-accidental-touch"), enter_pin_msg)
|
index: self.identity_index,
|
||||||
}
|
message: fl!(
|
||||||
// Otherwise, the PIN is either too short or too long.
|
"plugin-err-pin-required",
|
||||||
0..=5 => format!("{} {}", fl!("plugin-err-pin-too-short"), enter_pin_msg),
|
yubikey_serial = self.yubikey.serial().to_string(),
|
||||||
_ => format!("{} {}", fl!("plugin-err-pin-too-long"), enter_pin_msg),
|
),
|
||||||
},
|
}))
|
||||||
Err(_) => {
|
}
|
||||||
return Ok(Err(identity::Error::Identity {
|
|
||||||
index: self.identity_index,
|
|
||||||
message: fl!(
|
|
||||||
"plugin-err-pin-required",
|
|
||||||
yubikey_serial = self.yubikey.serial().to_string(),
|
|
||||||
),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
if let Err(e) = self.yubikey.verify_pin(pin.expose_secret().as_bytes()) {
|
if let Err(e) = self.yubikey.verify_pin(pin.expose_secret().as_bytes()) {
|
||||||
return Ok(Err(identity::Error::Identity {
|
return Ok(Err(identity::Error::Identity {
|
||||||
|
|||||||
Reference in New Issue
Block a user