8 Commits

Author SHA1 Message Date
str4d 307f5396a8 Merge pull request #124 from str4d/release-0.3.3
CI checks / Test on linux (push) Has been cancelled
CI checks / Test on macos (push) Has been cancelled
CI checks / Test on windows (push) Has been cancelled
CI checks / Clippy (1.56.0) (push) Has been cancelled
CI checks / Clippy (nightly) (push) Has been cancelled
CI checks / Code coverage (push) Has been cancelled
CI checks / Intra-doc links (push) Has been cancelled
CI checks / Rustfmt (push) Has been cancelled
Publish release binaries / Publish for macos-arm64 (push) Has been cancelled
Publish release binaries / Publish for macos-x86_64 (push) Has been cancelled
Publish release binaries / Publish for linux (push) Has been cancelled
Publish release binaries / Publish for windows (push) Has been cancelled
Publish release binaries / Debian linux (push) Has been cancelled
Release 0.3.3
2023-02-11 04:37:09 +00:00
Jack Grigg cd03e7bda3 Release 0.3.3 2023-02-11 04:28:16 +00:00
str4d 54ad666c73 Merge pull request #123 from str4d/120-prevent-default-pin
Prevent changing the default PIN to itself
2023-02-11 03:00:31 +00:00
Jack Grigg d2132b4ac2 Prevent changing the default PIN to itself
Closes str4d/age-plugin-yubikey#120.
2023-02-11 02:47:55 +00:00
str4d 80e8072624 Merge pull request #117 from str4d/more-smartcard-errors
Treat `pcsc::Error::NoSmartcard` as a "YubiKey disconnected" error
2023-02-11 02:18:34 +00:00
Jack Grigg ff3e8e37c9 Treat pcsc::Error::NoSmartcard as a "YubiKey disconnected" error
Some SmartCard readers report this error when no SmartCard is inserted,
so we need to check for it when filtering for connected YubiKeys (along
with `pcsc::Error::RemovedCard` which some _other_ SmartCard readers
report instead).

Closes str4d/age-plugin-yubikey#81.
2023-01-30 00:39:08 +00:00
str4d a5178bb16e Merge pull request #118 from str4d/correctly-handle-short-pins
Enforce correct PIN lengths during YubiKey setup
2023-01-30 00:37:44 +00:00
Jack Grigg b1710e8d69 Enforce correct PIN lengths during YubiKey setup
The behaviour of `age-plugin-yubikey` during setup now matches its
behaviour during plugin usage.
2023-01-29 23:00:46 +00:00
6 changed files with 89 additions and 48 deletions
+10
View File
@@ -8,6 +8,16 @@ to 0.3.0 are beta releases.
## [Unreleased]
## [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
### Changed
- The "sharing violation" logic now also sends SIGHUP to any `yubikey-agent`
Generated
+1 -1
View File
@@ -49,7 +49,7 @@ dependencies = [
[[package]]
name = "age-plugin-yubikey"
version = "0.3.2"
version = "0.3.3"
dependencies = [
"age-core",
"age-plugin",
+1 -1
View File
@@ -1,7 +1,7 @@
[package]
name = "age-plugin-yubikey"
description = "YubiKey plugin for age clients"
version = "0.3.2"
version = "0.3.3"
authors = ["Jack Grigg <thestr4d@gmail.com>"]
repository = "https://github.com/str4d/age-plugin-yubikey"
readme = "README.md"
+3 -2
View File
@@ -127,13 +127,15 @@ mgr-change-default-pin =
✨ Your {-yubikey} is using the default PIN. Let's change it!
✨ 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.
mgr-enter-current-puk = Enter current PUK (default is {$default_puk})
mgr-choose-new-pin = Choose a new PIN/PUK
mgr-repeat-new-pin = Repeat the PIN/PUK
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 =
✨ Your {-yubikey} is using the default management key.
@@ -180,7 +182,6 @@ rec-custom-mgmt-key =
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-pin-length = The PIN needs to be 1-8 characters.
err-invalid-pin-policy = Invalid PIN policy '{$policy}' (expected [{$expected}]).
err-invalid-slot = Invalid slot '{$slot}' (expected number between 1 and 20).
err-invalid-touch-policy = Invalid touch policy '{$policy}' (expected [{$expected}]).
-2
View File
@@ -17,7 +17,6 @@ pub enum Error {
CustomManagementKey,
InvalidFlagCommand(String, String),
InvalidFlagTui(String),
InvalidPinLength,
InvalidPinPolicy(String),
InvalidSlot(u8),
InvalidTouchPolicy(String),
@@ -63,7 +62,6 @@ impl fmt::Debug for Error {
command = command.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!(
f,
"err-invalid-pin-policy",
+59 -27
View File
@@ -3,12 +3,13 @@
use age_core::{
format::{FileKey, FILE_KEY_BYTES},
primitives::{aead_decrypt, hkdf},
secrecy::ExposeSecret,
secrecy::{ExposeSecret, SecretString},
};
use age_plugin::{identity, Callbacks};
use bech32::{ToBase32, Variant};
use dialoguer::Password;
use log::{debug, error, warn};
use std::convert::Infallible;
use std::fmt;
use std::io;
use std::iter;
@@ -40,7 +41,7 @@ pub(crate) fn is_connected(reader: Reader) -> bool {
pub(crate) fn filter_connected(reader: &Reader) -> bool {
match reader.open() {
Err(yubikey::Error::PcscError {
inner: Some(pcsc::Error::RemovedCard),
inner: Some(pcsc::Error::NoSmartcard | pcsc::Error::RemovedCard),
}) => {
warn!(
"{}",
@@ -236,6 +237,31 @@ pub(crate) fn open(serial: Option<Serial>) -> Result<YubiKey, Error> {
Ok(yubikey)
}
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> {
const DEFAULT_PIN: &str = "123456";
const DEFAULT_PUK: &str = "12345678";
@@ -258,13 +284,28 @@ pub(crate) fn manage(yubikey: &mut YubiKey) -> Result<(), Error> {
let current_puk = Password::new()
.with_prompt(fl!("mgr-enter-current-puk", default_puk = DEFAULT_PUK))
.interact()?;
let new_pin = Password::new()
let new_pin = loop {
let pin = request_pin(
|prev_error| {
if let Some(err) = prev_error {
eprintln!("{}", err);
}
Password::new()
.with_prompt(fl!("mgr-choose-new-pin"))
.with_confirmation(fl!("mgr-repeat-new-pin"), fl!("mgr-pin-mismatch"))
.interact()?;
if new_pin.len() > 8 {
return Err(Error::InvalidPinLength);
.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_pin(pin.as_bytes(), new_pin.as_bytes())?;
}
@@ -581,29 +622,21 @@ impl Connection {
// 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 enter_pin_msg = fl!(
let pin = match request_pin(
|prev_error| {
callbacks.request_secret(&format!(
"{}{}{}",
prev_error.as_deref().unwrap_or(""),
prev_error.as_deref().map(|_| " ").unwrap_or(""),
fl!(
"plugin-enter-pin",
yubikey_serial = self.yubikey.serial().to_string(),
);
let mut message = enter_pin_msg.clone();
let pin = loop {
message = match callbacks.request_secret(&message)? {
Ok(pin) => match pin.expose_secret().len() {
// A PIN must be between 6 and 8 characters.
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.
44 if pin
.expose_secret()
.starts_with(&otp_serial_prefix(self.yubikey.serial())) =>
{
format!("{} {}", fl!("plugin-err-accidental-touch"), enter_pin_msg)
}
// Otherwise, the PIN is either too short or too long.
0..=5 => format!("{} {}", fl!("plugin-err-pin-too-short"), enter_pin_msg),
_ => format!("{} {}", fl!("plugin-err-pin-too-long"), enter_pin_msg),
)
))
},
self.yubikey.serial(),
)? {
Ok(pin) => pin,
Err(_) => {
return Ok(Err(identity::Error::Identity {
index: self.identity_index,
@@ -614,7 +647,6 @@ impl Connection {
}))
}
};
};
if let Err(e) = self.yubikey.verify_pin(pin.expose_secret().as_bytes()) {
return Ok(Err(identity::Error::Identity {
index: self.identity_index,