Tag all strings for translation

This commit is contained in:
Jack Grigg
2022-05-01 12:55:48 +00:00
parent c4fe3f6b1a
commit a92a843e14
6 changed files with 541 additions and 230 deletions
+101 -48
View File
@@ -1,9 +1,16 @@
use i18n_embed_fl::fl;
use std::fmt;
use std::io;
use yubikey::{piv::RetiredSlotId, Serial};
use crate::util::slot_to_ui;
macro_rules! wlnfl {
($f:ident, $message_id:literal) => {
writeln!($f, "{}", $crate::fl!($message_id))
};
}
pub enum Error {
CustomManagementKey,
InvalidFlagCommand(String, String),
@@ -41,90 +48,136 @@ impl From<yubikey::Error> for Error {
impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::CustomManagementKey => {
writeln!(f, "Custom unprotected management keys are not supported.")?
}
Error::InvalidFlagCommand(flag, command) => {
writeln!(f, "Flag '{}' cannot be used with '{}'.", flag, command)?
}
Error::CustomManagementKey => wlnfl!(f, "err-custom-mgmt-key")?,
Error::InvalidFlagCommand(flag, command) => writeln!(
f,
"{}",
fl!(
crate::LANGUAGE_LOADER,
"err-invalid-flag-command",
flag = flag.as_str(),
command = command.as_str(),
),
)?,
Error::InvalidFlagTui(flag) => writeln!(
f,
"Flag '{}' cannot be used with the interactive interface.",
flag
"{}",
fl!(
crate::LANGUAGE_LOADER,
"err-invalid-flag-tui",
flag = flag.as_str(),
),
)?,
Error::InvalidPinLength => writeln!(f, "The PIN needs to be 1-8 characters.")?,
Error::InvalidPinLength => wlnfl!(f, "err-invalid-pin-length")?,
Error::InvalidPinPolicy(s) => writeln!(
f,
"Invalid PIN policy '{}' (expected [always, once, never]).",
s
"{}",
fl!(
crate::LANGUAGE_LOADER,
"err-invalid-pin-policy",
policy = s.as_str(),
expected = "always, once, never",
),
)?,
Error::InvalidSlot(slot) => writeln!(
f,
"Invalid slot '{}' (expected number between 1 and 20).",
slot
"{}",
fl!(crate::LANGUAGE_LOADER, "err-invalid-slot", slot = slot),
)?,
Error::InvalidTouchPolicy(s) => writeln!(
f,
"Invalid touch policy '{}' (expected [always, cached, never]).",
s
"{}",
fl!(
crate::LANGUAGE_LOADER,
"err-invalid-touch-policy",
policy = s.as_str(),
expected = "always, cached, never",
),
)?,
Error::Io(e) => writeln!(f, "Failed to set up YubiKey: {}", e)?,
Error::MultipleCommands => writeln!(
Error::Io(e) => writeln!(
f,
"Only one of --generate, --identity, --list, --list-all can be specified."
"{}",
fl!(crate::LANGUAGE_LOADER, "err-io", err = e.to_string()),
)?,
Error::MultipleYubiKeys => writeln!(
Error::MultipleCommands => wlnfl!(f, "err-multiple-commands")?,
Error::MultipleYubiKeys => wlnfl!(f, "err-multiple-yubikeys")?,
Error::NoEmptySlots(serial) => writeln!(
f,
"Multiple YubiKeys are plugged in. Use --serial to select a single YubiKey."
"{}",
fl!(
crate::LANGUAGE_LOADER,
"err-no-empty-slots",
serial = serial.to_string(),
),
)?,
Error::NoMatchingSerial(serial) => writeln!(
f,
"{}",
fl!(
crate::LANGUAGE_LOADER,
"err-no-matching-serial",
serial = serial.to_string(),
),
)?,
Error::NoEmptySlots(serial) => {
writeln!(f, "YubiKey with serial {} has no empty slots.", serial)?
}
Error::NoMatchingSerial(serial) => {
writeln!(f, "Could not find YubiKey with serial {}.", serial)?
}
Error::SlotHasNoIdentity(slot) => writeln!(
f,
"Slot {} does not contain an age identity or compatible key.",
slot_to_ui(slot)
"{}",
fl!(
crate::LANGUAGE_LOADER,
"err-slot-has-no-identity",
slot = slot_to_ui(slot),
),
)?,
Error::SlotIsNotEmpty(slot) => writeln!(
f,
"Slot {} is not empty. Use --force to overwrite the slot.",
slot_to_ui(slot)
"{}",
fl!(
crate::LANGUAGE_LOADER,
"err-slot-is-not-empty",
slot = slot_to_ui(slot),
),
)?,
Error::TimedOut => {
writeln!(f, "Timed out while waiting for a YubiKey to be inserted.")?
}
Error::UseListForSingleSlot => {
writeln!(f, "Use --list to print the recipient for a single slot.")?
}
Error::TimedOut => wlnfl!(f, "err-timed-out")?,
Error::UseListForSingleSlot => wlnfl!(f, "err-use-list-for-single")?,
Error::YubiKey(e) => match e {
yubikey::Error::NotFound => {
writeln!(f, "Please insert the YubiKey you want to set up")?
}
yubikey::Error::NotFound => wlnfl!(f, "err-yk-not-found")?,
yubikey::Error::WrongPin { tries } => writeln!(
f,
"Invalid PIN ({} tries remaining before it is blocked)",
tries
"{}",
fl!(crate::LANGUAGE_LOADER, "err-yk-wrong-pin", tries = tries),
)?,
e => {
writeln!(f, "Error while communicating with YubiKey: {}", e)?;
writeln!(
f,
"{}",
fl!(
crate::LANGUAGE_LOADER,
"err-yk-general",
err = e.to_string(),
),
)?;
use std::error::Error;
if let Some(inner) = e.source() {
writeln!(f, "Cause: {}", inner)?;
writeln!(
f,
"{}",
fl!(
crate::LANGUAGE_LOADER,
"err-yk-general-cause",
inner_err = inner.to_string(),
),
)?;
}
}
},
}
writeln!(f)?;
writeln!(
f,
"[ Did this not do what you expected? Could an error be more useful? ]"
)?;
writeln!(f, "[ {} ]", crate::fl!("err-ux-A"))?;
write!(
f,
"[ Tell us: https://str4d.xyz/age-plugin-yubikey/report ]"
"[ {}: https://str4d.xyz/age-plugin-yubikey/report {} ]",
crate::fl!("err-ux-B"),
crate::fl!("err-ux-C")
)
}
}
+84 -59
View File
@@ -23,6 +23,7 @@ use yubikey::{
use crate::{
error::Error,
fl,
format::{RecipientLine, STANZA_KEY_LABEL},
p256::{Recipient, TAG_BYTES},
util::{otp_serial_prefix, Metadata},
@@ -44,7 +45,14 @@ pub(crate) fn filter_connected(reader: &Reader) -> bool {
if let Some(pcsc::Error::RemovedCard) =
e.source().and_then(|inner| inner.downcast_ref())
{
warn!("Ignoring {}: not connected", reader.name());
warn!(
"{}",
i18n_embed_fl::fl!(
crate::LANGUAGE_LOADER,
"warn-yk-not-connected",
yubikey_name = reader.name(),
)
);
false
} else {
true
@@ -72,9 +80,16 @@ pub(crate) fn wait_for_readers() -> Result<Context, Error> {
pub(crate) fn open(serial: Option<Serial>) -> Result<YubiKey, Error> {
if !Context::open()?.iter()?.any(is_connected) {
if let Some(serial) = serial {
eprintln!("⏳ Please insert the YubiKey with serial {}.", serial);
eprintln!(
"{}",
i18n_embed_fl::fl!(
crate::LANGUAGE_LOADER,
"open-yk-with-serial",
yubikey_serial = serial.to_string(),
)
);
} else {
eprintln!("⏳ Please insert the YubiKey.");
eprintln!("{}", fl!("open-yk-without-serial"));
}
}
let mut readers = wait_for_readers()?;
@@ -111,32 +126,35 @@ pub(crate) fn open(serial: Option<Serial>) -> Result<YubiKey, Error> {
}
pub(crate) fn manage(yubikey: &mut YubiKey) -> Result<(), Error> {
const DEFAULT_PIN: &str = "123456";
const DEFAULT_PUK: &str = "12345678";
eprintln!();
let pin = Password::new()
.with_prompt(&format!(
"Enter PIN for YubiKey with serial {} (default is 123456)",
yubikey.serial(),
.with_prompt(i18n_embed_fl::fl!(
crate::LANGUAGE_LOADER,
"mgr-enter-pin",
yubikey_serial = yubikey.serial().to_string(),
default_pin = DEFAULT_PIN,
))
.interact()?;
yubikey.verify_pin(pin.as_bytes())?;
// If the user is using the default PIN, help them to change it.
if pin == "123456" {
if pin == DEFAULT_PIN {
eprintln!();
eprintln!("✨ Your YubiKey is using the default PIN. Let's change it!");
eprintln!("✨ We'll also set the PUK equal to the PIN.");
eprintln!();
eprintln!("🔐 The PIN is up to 8 numbers, letters, or symbols. Not just numbers!");
eprintln!(
"❌ Your keys will be lost if the PIN and PUK are locked after 3 incorrect tries."
);
eprintln!("{}", fl!("mgr-change-default-pin"));
eprintln!();
let current_puk = Password::new()
.with_prompt("Enter current PUK (default is 12345678)")
.with_prompt(i18n_embed_fl::fl!(
crate::LANGUAGE_LOADER,
"mgr-enter-current-puk",
default_puk = DEFAULT_PUK,
))
.interact()?;
let new_pin = Password::new()
.with_prompt("Choose a new PIN/PUK")
.with_confirmation("Repeat the PIN/PUK", "PINs don't match")
.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);
@@ -156,16 +174,20 @@ pub(crate) fn manage(yubikey: &mut YubiKey) -> Result<(), Error> {
// Migrate to a PIN-protected management key.
let mgm_key = MgmKey::generate();
eprintln!();
eprintln!("✨ Your YubiKey is using the default management key.");
eprintln!("✨ We'll migrate it to a PIN-protected management key.");
eprintln!("{}", fl!("mgr-changing-mgmt-key"));
eprint!("... ");
mgm_key.set_protected(yubikey).map_err(|e| {
eprintln!("An error occurred while setting the new management key.");
eprintln!("⚠️ SAVE THIS MANAGEMENT KEY - YOU MAY NEED IT TO MANAGE YOUR YubiKey! ⚠️");
eprintln!(" {}", hex::encode(mgm_key.as_ref()));
eprintln!(
"{}",
i18n_embed_fl::fl!(
crate::LANGUAGE_LOADER,
"mgr-changing-mgmt-key-error",
management_key = hex::encode(mgm_key.as_ref()),
)
);
e
})?;
eprintln!("Success!");
eprintln!("{}", fl!("mgr-changing-mgmt-key-success"));
}
Ok(())
@@ -246,15 +268,20 @@ impl Stub {
Ok(yk) => yk,
Err(yubikey::Error::NotFound) => {
if callbacks
.message(&format!(
"Please insert YubiKey with serial {}",
self.serial
.message(&i18n_embed_fl::fl!(
crate::LANGUAGE_LOADER,
"plugin-insert-yk",
yubikey_serial = self.serial.to_string(),
))?
.is_err()
{
return Ok(Err(identity::Error::Identity {
index: self.identity_index,
message: format!("Could not find YubiKey with serial {}", self.serial),
message: i18n_embed_fl::fl!(
crate::LANGUAGE_LOADER,
"plugin-err-yk-not-found",
yubikey_serial = self.serial.to_string(),
),
}));
}
@@ -267,9 +294,10 @@ impl Stub {
Err(_) => {
return Ok(Err(identity::Error::Identity {
index: self.identity_index,
message: format!(
"Could not open YubiKey with serial {}",
self.serial
message: i18n_embed_fl::fl!(
crate::LANGUAGE_LOADER,
"plugin-err-yk-opening",
yubikey_serial = self.serial.to_string(),
),
}));
}
@@ -279,10 +307,11 @@ impl Stub {
Ok(end) if end >= FIFTEEN_SECONDS => {
return Ok(Err(identity::Error::Identity {
index: self.identity_index,
message: format!(
"Timed out while waiting for YubiKey with serial {} to be inserted",
self.serial
),
message: i18n_embed_fl::fl!(
crate::LANGUAGE_LOADER,
"plugin-err-yk-timed-out",
yubikey_serial = self.serial.to_string(),
),
}))
}
_ => sleep(ONE_SECOND),
@@ -292,7 +321,11 @@ impl Stub {
Err(_) => {
return Ok(Err(identity::Error::Identity {
index: self.identity_index,
message: format!("Could not open YubiKey with serial {}", self.serial),
message: i18n_embed_fl::fl!(
crate::LANGUAGE_LOADER,
"plugin-err-yk-opening",
yubikey_serial = self.serial.to_string(),
),
}))
}
};
@@ -310,7 +343,7 @@ impl Stub {
None => {
return Ok(Err(identity::Error::Identity {
index: self.identity_index,
message: "A YubiKey stub did not match the YubiKey".to_owned(),
message: fl!("plugin-err-yk-stub-mismatch"),
}))
}
};
@@ -356,9 +389,7 @@ impl Connection {
None => {
return Ok(Err(identity::Error::Identity {
index: self.identity_index,
message:
"Certificate for YubiKey identity contains an invalid PIN policy"
.to_string(),
message: fl!("plugin-err-yk-invalid-pin-policy"),
}))
}
metadata => metadata,
@@ -371,10 +402,12 @@ 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 mut message = format!(
"Enter PIN for YubiKey with serial {}",
self.yubikey.serial()
let enter_pin_msg = i18n_embed_fl::fl!(
crate::LANGUAGE_LOADER,
"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() {
@@ -387,27 +420,19 @@ impl Connection {
.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()
)
format!("{} {}", fl!("plugin-err-accidental-touch"), enter_pin_msg)
}
// 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()
),
0..=5 => format!("{} {}", fl!("plugin-err-pin-too-short"), enter_pin_msg),
_ => format!("{} {}", fl!("plugin-err-pin-too-long"), enter_pin_msg),
},
Err(_) => {
return Ok(Err(identity::Error::Identity {
index: self.identity_index,
message: format!(
"A PIN is required for YubiKey with serial {}",
self.yubikey.serial()
message: i18n_embed_fl::fl!(
crate::LANGUAGE_LOADER,
"plugin-err-pin-required",
yubikey_serial = self.yubikey.serial().to_string(),
),
}))
}
@@ -435,11 +460,11 @@ impl Connection {
self.last_touch,
) {
(Some(TouchPolicy::Always), _) | (Some(TouchPolicy::Cached), None) => {
callbacks.message("👆 Please touch the YubiKey")?.unwrap();
callbacks.message(&fl!("plugin-touch-yk"))?.unwrap();
true
}
(Some(TouchPolicy::Cached), Some(last)) if last.elapsed() >= FIFTEEN_SECONDS => {
callbacks.message("👆 Please touch the YubiKey")?.unwrap();
callbacks.message(&fl!("plugin-touch-yk"))?.unwrap();
true
}
_ => false,
+128 -90
View File
@@ -269,8 +269,13 @@ fn print_multiple(
}
if printed > 1 {
eprintln!(
"Generated {} for {} slots. If you intended to select a slot, use --slot.",
kind, printed,
"{}",
i18n_embed_fl::fl!(
LANGUAGE_LOADER,
"printed-multiple",
kind = kind,
count = printed,
)
);
}
@@ -297,7 +302,12 @@ fn identity(flags: PluginFlags) -> Result<(), Error> {
"--identity".into(),
));
}
print_details("identities", flags, false, util::print_identity)
print_details(
&fl!("printed-kind-identities"),
flags,
false,
util::print_identity,
)
}
fn list(flags: PluginFlags, all: bool) -> Result<(), Error> {
@@ -311,10 +321,15 @@ fn list(flags: PluginFlags, all: bool) -> Result<(), Error> {
));
}
print_details("recipients", flags, all, |_, recipient, metadata| {
println!("{}", metadata);
println!("{}", recipient.to_string());
})
print_details(
&fl!("printed-kind-recipients"),
flags,
all,
|_, recipient, metadata| {
println!("{}", metadata);
println!("{}", recipient.to_string());
},
)
}
fn main() -> Result<(), Error> {
@@ -365,23 +380,18 @@ fn main() -> Result<(), Error> {
}
let flags: PluginFlags = opts.try_into()?;
eprintln!("✨ Let's get your YubiKey set up for age! ✨");
eprintln!();
eprintln!("This tool can create a new age identity in a free slot of your YubiKey.");
eprintln!("It will generate an identity file that you can use with an age client,");
eprintln!("along with the corresponding recipient. You can also do this directly");
eprintln!("with:");
eprintln!(" age-plugin-yubikey --generate");
eprintln!();
eprintln!("If you are already using a YubiKey with age, you can select an existing");
eprintln!("slot to recreate its corresponding identity file and recipient.");
eprintln!();
eprintln!("When asked below to select an option, use the up/down arrow keys to");
eprintln!("make your choice, or press [Esc] or [q] to quit.");
eprintln!(
"{}",
i18n_embed_fl::fl!(
LANGUAGE_LOADER,
"cli-setup-intro",
generate_usage = "age-plugin-yubikey --generate",
)
);
eprintln!();
if !Context::open()?.iter()?.any(key::is_connected) {
eprintln!("⏳ Please insert the YubiKey you want to set up.");
eprintln!("{}", fl!("cli-setup-insert-yk"));
};
let mut readers = key::wait_for_readers()?;
@@ -391,13 +401,18 @@ fn main() -> Result<(), Error> {
let reader_names = readers_list
.iter()
.map(|reader| {
reader
.open()
.map(|yk| format!("{} (Serial: {})", reader.name(), yk.serial()))
reader.open().map(|yk| {
i18n_embed_fl::fl!(
LANGUAGE_LOADER,
"cli-setup-yk-name",
yubikey_name = reader.name(),
yubikey_serial = yk.serial().to_string(),
)
})
})
.collect::<Result<Vec<_>, _>>()?;
let mut yubikey = match Select::new()
.with_prompt("🔑 Select a YubiKey")
.with_prompt(fl!("cli-setup-select-yk"))
.items(&reader_names)
.default(0)
.interact_opt()?
@@ -440,9 +455,20 @@ fn main() -> Result<(), Error> {
let i = i + 1;
match occupied {
Some(Some(name)) => format!("Slot {} ({})", i, name),
Some(None) => format!("Slot {} (Unusable)", i),
None => format!("Slot {} (Empty)", i),
Some(Some(name)) => i18n_embed_fl::fl!(
LANGUAGE_LOADER,
"cli-setup-slot-usable",
slot_index = i,
slot_name = name.as_str(),
),
Some(None) => i18n_embed_fl::fl!(
LANGUAGE_LOADER,
"cli-setup-slot-unusable",
slot_index = i,
),
None => {
i18n_embed_fl::fl!(LANGUAGE_LOADER, "cli-setup-slot-empty", slot_index = i)
}
}
})
.collect();
@@ -450,7 +476,7 @@ fn main() -> Result<(), Error> {
let ((stub, recipient, metadata), is_new) = {
let (slot_index, slot) = loop {
match Select::new()
.with_prompt("🕳️ Select a slot for your age identity")
.with_prompt(fl!("cli-setup-select-slot"))
.items(&slots)
.default(0)
.interact_opt()?
@@ -474,7 +500,11 @@ fn main() -> Result<(), Error> {
};
if Confirm::new()
.with_prompt(&format!("Use existing identity in slot {}?", slot_index))
.with_prompt(i18n_embed_fl::fl!(
LANGUAGE_LOADER,
"cli-setup-use-existing",
slot_index = slot_index,
))
.interact()?
{
let stub = key::Stub::new(yubikey.serial(), slot, &recipient);
@@ -490,18 +520,19 @@ fn main() -> Result<(), Error> {
} else {
let name = Input::<String>::new()
.with_prompt(format!(
"📛 Name this identity [{}]",
"{} [{}]",
fl!("cli-setup-name-identity"),
flags.name.as_deref().unwrap_or("age identity TAG_HEX")
))
.allow_empty(true)
.interact_text()?;
let pin_policy = match Select::new()
.with_prompt("🔤 Select a PIN policy")
.with_prompt(fl!("cli-setup-select-pin-policy"))
.items(&[
"Always (A PIN is required for every decryption, if set)",
"Once (A PIN is required once per session, if set)",
"Never (A PIN is NOT required to decrypt)",
fl!("pin-policy-always"),
fl!("pin-policy-once"),
fl!("pin-policy-never"),
])
.default(
[PinPolicy::Always, PinPolicy::Once, PinPolicy::Never]
@@ -521,30 +552,35 @@ fn main() -> Result<(), Error> {
};
let touch_policy = match Select::new()
.with_prompt("👆 Select a touch policy")
.items(&[
"Always (A physical touch is required for every decryption)",
"Cached (A physical touch is required for decryption, and is cached for 15 seconds)",
"Never (A physical touch is NOT required to decrypt)",
])
.default(
[TouchPolicy::Always, TouchPolicy::Cached, TouchPolicy::Never]
.iter()
.position(|p| p == &flags
.touch_policy.unwrap_or(builder::DEFAULT_TOUCH_POLICY))
.unwrap(),
)
.interact_opt()?
{
Some(0) => TouchPolicy::Always,
Some(1) => TouchPolicy::Cached,
Some(2) => TouchPolicy::Never,
Some(_) => unreachable!(),
None => return Ok(()),
};
.with_prompt(fl!("cli-setup-select-touch-policy"))
.items(&[
fl!("touch-policy-always"),
fl!("touch-policy-cached"),
fl!("touch-policy-never"),
])
.default(
[TouchPolicy::Always, TouchPolicy::Cached, TouchPolicy::Never]
.iter()
.position(|p| {
p == &flags.touch_policy.unwrap_or(builder::DEFAULT_TOUCH_POLICY)
})
.unwrap(),
)
.interact_opt()?
{
Some(0) => TouchPolicy::Always,
Some(1) => TouchPolicy::Cached,
Some(2) => TouchPolicy::Never,
Some(_) => unreachable!(),
None => return Ok(()),
};
if Confirm::new()
.with_prompt(&format!("Generate new identity in slot {}?", slot_index))
.with_prompt(i18n_embed_fl::fl!(
LANGUAGE_LOADER,
"cli-setup-generate-new",
slot_index = slot_index,
))
.interact()?
{
eprintln!();
@@ -567,7 +603,7 @@ fn main() -> Result<(), Error> {
eprintln!();
let file_name = Input::<String>::new()
.with_prompt("📝 File name to write this identity to")
.with_prompt(fl!("cli-setup-identity-file-name"))
.default(format!(
"age-yubikey-identity-{}.txt",
hex::encode(stub.tag)
@@ -582,7 +618,7 @@ fn main() -> Result<(), Error> {
Ok(file) => file,
Err(e) if e.kind() == io::ErrorKind::AlreadyExists => {
if Confirm::new()
.with_prompt("File exists. Overwrite it?")
.with_prompt(fl!("cli-setup-identity-file-exists"))
.interact()?
{
File::create(&file_name)?
@@ -593,54 +629,56 @@ fn main() -> Result<(), Error> {
Err(e) => return Err(e.into()),
};
writeln!(file, "{}", metadata)?;
writeln!(file, "# Recipient: {}", recipient)?;
writeln!(file, "{}", stub.to_string())?;
writeln!(
file,
"{}",
i18n_embed_fl::fl!(
LANGUAGE_LOADER,
"yubikey-identity",
yubikey_metadata = metadata.to_string(),
recipient = recipient.to_string(),
identity = stub.to_string(),
)
)?;
file.sync_data()?;
// If `rage` binary is installed, use it in examples. Otherwise default to `age`.
let age_binary = which::which("rage").map(|_| "rage").unwrap_or("age");
eprintln!();
eprintln!("✅ Done! This YubiKey identity is ready to go.");
eprintln!();
if is_new {
eprintln!("🔑 Here's your shiny new YubiKey recipient:");
} else {
eprintln!("🔑 Here's the corresponding YubiKey recipient:");
}
eprintln!(" {}", recipient);
eprintln!();
eprintln!("Here are some example things you can do with it:");
eprintln!();
eprintln!("- Encrypt a file to this identity:");
eprintln!(
" $ cat foo.txt | {} -r {} -o foo.txt.age",
let encrypt_usage = format!(
"$ cat foo.txt | {} -r {} -o foo.txt.age",
age_binary, recipient
);
eprintln!();
eprintln!("- Decrypt a file with this identity:");
eprintln!(
" $ cat foo.txt.age | {} -d -i {} > foo.txt",
let decrypt_usage = format!(
"$ cat foo.txt.age | {} -d -i {} > foo.txt",
age_binary, file_name
);
eprintln!();
eprintln!("- Recreate the identity file:");
eprintln!(
" $ age-plugin-yubikey -i --serial {} --slot {} > {}",
let identity_usage = format!(
"$ age-plugin-yubikey -i --serial {} --slot {} > {}",
stub.serial,
util::slot_to_ui(&stub.slot),
file_name,
);
eprintln!();
eprintln!("- Recreate the recipient:");
eprintln!(
" $ age-plugin-yubikey -l --serial {} --slot {}",
let recipient_usage = format!(
"$ age-plugin-yubikey -l --serial {} --slot {}",
stub.serial,
util::slot_to_ui(&stub.slot),
);
eprintln!();
eprintln!("💭 Remember: everything breaks, have a backup plan for when this YubiKey does.");
eprintln!(
"{}",
i18n_embed_fl::fl!(
LANGUAGE_LOADER,
"cli-setup-finished",
is_new = if is_new { "true" } else { "false" },
recipient = recipient.to_string(),
encrypt_usage = encrypt_usage,
decrypt_usage = decrypt_usage,
identity_usage = identity_usage,
recipient_usage = recipient_usage,
)
);
Ok(())
}
+6 -6
View File
@@ -7,7 +7,7 @@ use age_plugin::{
use std::collections::HashMap;
use std::io;
use crate::{format, key, p256::Recipient, PLUGIN_NAME};
use crate::{fl, format, key, p256::Recipient, PLUGIN_NAME};
#[derive(Debug, Default)]
pub(crate) struct RecipientPlugin {
@@ -32,7 +32,7 @@ impl RecipientPluginV1 for RecipientPlugin {
} else {
Err(recipient::Error::Recipient {
index,
message: "Invalid recipient".to_owned(),
message: fl!("plugin-err-invalid-recipient"),
})
}
}
@@ -53,7 +53,7 @@ impl RecipientPluginV1 for RecipientPlugin {
} else {
Err(recipient::Error::Identity {
index,
message: "Invalid Yubikey stub".to_owned(),
message: fl!("plugin-err-invalid-identity"),
})
}
}
@@ -120,7 +120,7 @@ impl IdentityPluginV1 for IdentityPlugin {
} else {
Err(identity::Error::Identity {
index,
message: "Invalid Yubikey stub".to_owned(),
message: fl!("plugin-err-invalid-identity"),
})
}
}
@@ -146,7 +146,7 @@ impl IdentityPluginV1 for IdentityPlugin {
res.map_err(|_| identity::Error::Stanza {
file_index: file,
stanza_index,
message: "Invalid yubikey stanza".to_owned(),
message: fl!("plugin-err-invalid-stanza"),
})
}),
file_keys.contains_key(&file),
@@ -232,7 +232,7 @@ impl IdentityPluginV1 for IdentityPlugin {
.error(identity::Error::Stanza {
file_index,
stanza_index,
message: "Failed to decrypt YubiKey stanza".to_owned(),
message: fl!("plugin-err-decryption-failed"),
})?
.unwrap(),
}
+40 -27
View File
@@ -7,6 +7,7 @@ use yubikey::{
PinPolicy, Serial, TouchPolicy, YubiKey,
};
use crate::fl;
use crate::{error::Error, key::Stub, p256::Recipient, BINARY_NAME, USABLE_SLOTS};
pub(crate) const POLICY_EXTENSION_OID: &[u64] = &[1, 3, 6, 1, 4, 1, 41482, 3, 8];
@@ -42,23 +43,21 @@ pub(crate) fn touch_policy_from_string(s: String) -> Result<TouchPolicy, Error>
}
}
pub(crate) fn pin_policy_to_str(policy: Option<PinPolicy>) -> &'static str {
pub(crate) fn pin_policy_to_str(policy: Option<PinPolicy>) -> String {
match policy {
Some(PinPolicy::Always) => "Always (A PIN is required for every decryption, if set)",
Some(PinPolicy::Once) => "Once (A PIN is required once per session, if set)",
Some(PinPolicy::Never) => "Never (A PIN is NOT required to decrypt)",
_ => "Unknown",
Some(PinPolicy::Always) => fl!("pin-policy-always"),
Some(PinPolicy::Once) => fl!("pin-policy-once"),
Some(PinPolicy::Never) => fl!("pin-policy-never"),
_ => fl!("unknown-policy"),
}
}
pub(crate) fn touch_policy_to_str(policy: Option<TouchPolicy>) -> &'static str {
pub(crate) fn touch_policy_to_str(policy: Option<TouchPolicy>) -> String {
match policy {
Some(TouchPolicy::Always) => "Always (A physical touch is required for every decryption)",
Some(TouchPolicy::Cached) => {
"Cached (A physical touch is required for decryption, and is cached for 15 seconds)"
}
Some(TouchPolicy::Never) => "Never (A physical touch is NOT required to decrypt)",
_ => "Unknown",
Some(TouchPolicy::Always) => fl!("touch-policy-always"),
Some(TouchPolicy::Cached) => fl!("touch-policy-cached"),
Some(TouchPolicy::Never) => fl!("touch-policy-never"),
_ => fl!("unknown-policy"),
}
}
@@ -178,19 +177,19 @@ impl Metadata {
impl fmt::Display for Metadata {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(
f,
"# Serial: {}, Slot: {}",
self.serial,
slot_to_ui(&self.slot)
)?;
writeln!(f, "# Name: {}", self.name)?;
writeln!(f, "# Created: {}", self.created)?;
writeln!(f, "# PIN policy: {}", pin_policy_to_str(self.pin_policy))?;
write!(
f,
"# Touch policy: {}",
touch_policy_to_str(self.touch_policy)
"{}",
i18n_embed_fl::fl!(
crate::LANGUAGE_LOADER,
"yubikey-metadata",
serial = self.serial.to_string(),
slot = slot_to_ui(&self.slot),
name = self.name.as_str(),
created = self.created.as_str(),
pin_policy = pin_policy_to_str(self.pin_policy),
touch_policy = touch_policy_to_str(self.touch_policy),
)
)
}
}
@@ -198,10 +197,24 @@ impl fmt::Display for Metadata {
pub(crate) fn print_identity(stub: Stub, recipient: Recipient, metadata: Metadata) {
let recipient = recipient.to_string();
if !console::user_attended() {
eprintln!("Recipient: {}", recipient);
eprintln!(
"{}",
i18n_embed_fl::fl!(
crate::LANGUAGE_LOADER,
"print-recipient",
recipient = recipient.as_str(),
)
);
}
println!("{}", metadata);
println!("# Recipient: {}", recipient);
println!("{}", stub.to_string());
println!(
"{}",
i18n_embed_fl::fl!(
crate::LANGUAGE_LOADER,
"yubikey-identity",
yubikey_metadata = metadata.to_string(),
recipient = recipient,
identity = stub.to_string(),
)
);
}