Detect invalid PIN lengths and ask the user again
We also detect the specific case where the PIN returned by the user is likely a YubiKey OTP, generated by the user touching it early. Closes str4d/age-plugin-yubikey#37.
This commit is contained in:
+38
-11
@@ -26,7 +26,7 @@ use crate::{
|
|||||||
error::Error,
|
error::Error,
|
||||||
format::{RecipientLine, STANZA_KEY_LABEL},
|
format::{RecipientLine, STANZA_KEY_LABEL},
|
||||||
p256::{Recipient, TAG_BYTES},
|
p256::{Recipient, TAG_BYTES},
|
||||||
util::Metadata,
|
util::{otp_serial_prefix, Metadata},
|
||||||
IDENTITY_PREFIX,
|
IDENTITY_PREFIX,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -365,20 +365,47 @@ impl Connection {
|
|||||||
// The policy requires a PIN, so request it.
|
// The policy requires a PIN, so request it.
|
||||||
// Note that we can't distinguish between PinPolicy::Once and PinPolicy::Always
|
// Note that we can't distinguish between PinPolicy::Once and PinPolicy::Always
|
||||||
// because this plugin is ephemeral, so we always request the PIN.
|
// because this plugin is ephemeral, so we always request the PIN.
|
||||||
let pin = match callbacks.request_secret(&format!(
|
let mut message = format!(
|
||||||
"Enter PIN for YubiKey with serial {}",
|
"Enter PIN for YubiKey with serial {}",
|
||||||
self.yubikey.serial()
|
self.yubikey.serial()
|
||||||
))? {
|
);
|
||||||
Ok(pin) => pin,
|
let pin = loop {
|
||||||
Err(_) => {
|
message = match callbacks.request_secret(&message)? {
|
||||||
return Ok(Err(identity::Error::Identity {
|
Ok(pin) => match pin.expose_secret().len() {
|
||||||
index: self.identity_index,
|
// A PIN must be between 6 and 8 characters.
|
||||||
message: format!(
|
6..=8 => break pin,
|
||||||
"A PIN is required for YubiKey with serial {}",
|
// 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!(
|
||||||
|
"Did you touch the YubiKey by accident? Enter PIN for YubiKey with serial {}",
|
||||||
|
self.yubikey.serial()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// Otherwise, the PIN is either too short or too long.
|
||||||
|
0..=5 => format!(
|
||||||
|
"PIN was too short. Enter PIN for YubiKey with serial {}",
|
||||||
self.yubikey.serial()
|
self.yubikey.serial()
|
||||||
),
|
),
|
||||||
}))
|
_ => format!(
|
||||||
}
|
"PIN was too long. Enter PIN for YubiKey with serial {}",
|
||||||
|
self.yubikey.serial()
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Err(_) => {
|
||||||
|
return Ok(Err(identity::Error::Identity {
|
||||||
|
index: self.identity_index,
|
||||||
|
message: format!(
|
||||||
|
"A PIN is required for YubiKey with serial {}",
|
||||||
|
self.yubikey.serial()
|
||||||
|
),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
};
|
||||||
};
|
};
|
||||||
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 {
|
||||||
|
|||||||
+10
@@ -1,4 +1,5 @@
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use std::iter;
|
||||||
|
|
||||||
use x509_parser::{certificate::X509Certificate, der_parser::oid::Oid};
|
use x509_parser::{certificate::X509Certificate, der_parser::oid::Oid};
|
||||||
use yubikey::{
|
use yubikey::{
|
||||||
@@ -61,6 +62,15 @@ pub(crate) fn touch_policy_to_str(policy: Option<TouchPolicy>) -> &'static str {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MODHEX: &str = "cbdefghijklnrtuv";
|
||||||
|
pub(crate) fn otp_serial_prefix(serial: Serial) -> String {
|
||||||
|
iter::repeat(0)
|
||||||
|
.take(4)
|
||||||
|
.chain((0..8).rev().map(|i| (serial.0 >> (4 * i)) & 0x0f))
|
||||||
|
.map(|i| MODHEX.char_indices().nth(i as usize).unwrap().1)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn extract_name(cert: &X509Certificate, all: bool) -> Option<(String, bool)> {
|
pub(crate) fn extract_name(cert: &X509Certificate, all: bool) -> Option<(String, bool)> {
|
||||||
// Look at Subject Organization to determine if we created this.
|
// Look at Subject Organization to determine if we created this.
|
||||||
match cert.subject().iter_organization().next() {
|
match cert.subject().iter_organization().next() {
|
||||||
|
|||||||
Reference in New Issue
Block a user