diff --git a/src/key.rs b/src/key.rs index b911a56..84919f3 100644 --- a/src/key.rs +++ b/src/key.rs @@ -26,7 +26,7 @@ use crate::{ error::Error, format::{RecipientLine, STANZA_KEY_LABEL}, p256::{Recipient, TAG_BYTES}, - util::Metadata, + util::{otp_serial_prefix, Metadata}, IDENTITY_PREFIX, }; @@ -365,20 +365,47 @@ 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 pin = match callbacks.request_secret(&format!( + let mut message = format!( "Enter PIN for YubiKey with serial {}", self.yubikey.serial() - ))? { - Ok(pin) => pin, - Err(_) => { - return Ok(Err(identity::Error::Identity { - index: self.identity_index, - message: format!( - "A PIN is required for YubiKey with serial {}", + ); + 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!( + "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() ), - })) - } + _ => 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()) { return Ok(Err(identity::Error::Identity { diff --git a/src/util.rs b/src/util.rs index f83a252..1298e07 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,4 +1,5 @@ use std::fmt; +use std::iter; use x509_parser::{certificate::X509Certificate, der_parser::oid::Oid}; use yubikey::{ @@ -61,6 +62,15 @@ pub(crate) fn touch_policy_to_str(policy: Option) -> &'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)> { // Look at Subject Organization to determine if we created this. match cert.subject().iter_organization().next() {