From eb7daab9e6369f9ff5b5f9a509731d5c83906c4e Mon Sep 17 00:00:00 2001 From: Philip Tricca Date: Sun, 1 Dec 2024 13:12:51 -0800 Subject: [PATCH 1/5] Add types for SecretWriter & Reader, clean up command line. Adding traits for SecretWriters and Readers gives us better structure that will allow us to add new ones. Changes to the command line / clap interface are an attempt to handle the special cases that make assumptions about the state of the YubiHSM more clear / clean. --- src/bin/printer-test.rs | 4 +- src/main.rs | 216 +++++++++++++++++++++++++++------------- src/secret_reader.rs | 53 +++++++++- src/secret_writer.rs | 51 +++++++++- 4 files changed, 250 insertions(+), 74 deletions(-) diff --git a/src/bin/printer-test.rs b/src/bin/printer-test.rs index 0d8e46c..19cc1b9 100644 --- a/src/bin/printer-test.rs +++ b/src/bin/printer-test.rs @@ -8,7 +8,9 @@ use anyhow::Result; use clap::{Parser, Subcommand}; use hex::ToHex; use oks::{ - alphabet::Alphabet, backup::Share, secret_writer::PrinterSecretWriter, + alphabet::Alphabet, + backup::Share, + secret_writer::{PrinterSecretWriter, SecretWriter}, }; use rand::thread_rng; use zeroize::Zeroizing; diff --git a/src/main.rs b/src/main.rs index a46a70f..3d61a9a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,8 +25,8 @@ use oks::{ ENV_NEW_PASSWORD, ENV_PASSWORD, KEYSPEC_EXT, }, hsm::Hsm, - secret_reader::{StdioPasswordReader, StdioShareReader}, - secret_writer::{PrinterSecretWriter, DEFAULT_PRINT_DEV}, + secret_reader::{self, PasswordReader, SecretInput, StdioPasswordReader}, + secret_writer::{self, SecretOutput, DEFAULT_PRINT_DEV}, util, }; @@ -72,6 +72,9 @@ struct Args { #[derive(Subcommand, Debug, PartialEq)] enum Command { Ca { + #[clap(long, env)] + auth_method: SecretInput, + #[command(subcommand)] command: CaCommand, }, @@ -105,8 +108,11 @@ enum Command { )] pkcs11_path: PathBuf, - #[clap(long, env, default_value = DEFAULT_PRINT_DEV)] - print_dev: PathBuf, + #[clap(long, env)] + secret_method: SecretOutput, + + #[clap(long, env, required = false, default_value = DEFAULT_PRINT_DEV)] + secret_dev: PathBuf, #[clap(long, env)] /// Challenge the caller for a new password, don't generate a @@ -153,32 +159,47 @@ enum CaCommand { enum HsmCommand { /// Generate keys in YubiHSM from specification. Generate { + #[clap(long, env)] + auth_method: SecretInput, + #[clap(long, env, default_value = "input")] key_spec: PathBuf, }, /// Initialize the YubiHSM for use in the OKS. + // assume default auth for passwd, generate passwd w/ alphabet or stdin, + // choose share dst: printer / cdr Initialize { - #[clap(long, env, default_value = "/dev/usb/lp0")] - print_dev: PathBuf, - #[clap(long, env)] /// Challenge the caller for a new password, don't generate a /// random one for them. passwd_challenge: bool, + + #[clap(long, env)] + secret_method: SecretOutput, + + #[clap(long, env, required = false, default_value = DEFAULT_PRINT_DEV)] + secret_dev: PathBuf, }, /// Restore a previously split aes256-ccm-wrap key + // assume default auth for passwd, chose share src: stdio / cdr Restore { #[clap(long, env, default_value = "input")] backups: PathBuf, + #[clap(long, env)] + share_method: SecretInput, + #[clap(long, env, default_value = "input/verifier.json")] verifier: PathBuf, }, /// Get serial number from YubiHSM and dump to console. - SerialNumber, + SerialNumber { + #[clap(long, env)] + auth_method: SecretInput, + }, } fn make_dir(path: &Path) -> Result<()> { @@ -208,12 +229,9 @@ fn get_auth_id(auth_id: Option, command: &HsmCommand) -> Id { // for these HSM commands we assume YubiHSM2 is in its // default state and we use the default auth credentials: // auth_id 1 - HsmCommand::Initialize { - print_dev: _, - passwd_challenge: _, - } + HsmCommand::Initialize { .. } | HsmCommand::Restore { .. } - | HsmCommand::SerialNumber => 1, + | HsmCommand::SerialNumber { .. } => 1, // otherwise we assume the auth key that we create is // present: auth_id 2 _ => 2, @@ -225,12 +243,14 @@ fn get_auth_id(auth_id: Option, command: &HsmCommand) -> Id { /// the user with a password prompt. fn get_passwd( auth_id: Option, + auth_method: &SecretInput, command: &HsmCommand, ) -> Result> { let passwd = match env::var(ENV_PASSWORD).ok() { Some(s) => Zeroizing::new(s), None => { - let passwd_reader = StdioPasswordReader::default(); + let passwd_reader = secret_reader::get_passwd_reader(*auth_method); + if auth_id.is_some() { // if auth_id was set by the caller but not the password we // prompt for the password @@ -241,12 +261,9 @@ fn get_passwd( // the command is one of these, we assume the // YubiHSM2 is in its default state so we use the // default password - HsmCommand::Initialize { - print_dev: _, - passwd_challenge: _, - } + HsmCommand::Initialize { .. } | HsmCommand::Restore { .. } - | HsmCommand::SerialNumber => { + | HsmCommand::SerialNumber { .. } => { Zeroizing::new("password".to_string()) } // otherwise prompt the user for the password @@ -302,13 +319,14 @@ fn do_ceremony>( csr_spec: P, key_spec: P, pkcs11_path: P, - print_dev: P, + secret_method: &SecretOutput, + secret_dev: P, challenge: bool, args: &Args, ) -> Result<()> { let passwd_new = { // assume YubiHSM is in default state: use default auth credentials - let passwd = "password".to_string(); + let passwd = Zeroizing::new("password".to_string()); let mut hsm = Hsm::new( 1, &passwd, @@ -338,10 +356,11 @@ fn do_ceremony>( custodian is present in front of the printer.\n\n\ Press enter to begin the key share recording process ...", LIMIT, - print_dev.as_ref().display(), + secret_dev.as_ref().display(), ); - let secret_writer = PrinterSecretWriter::new(Some(print_dev)); + let secret_writer = + secret_writer::get_writer(*secret_method, Some(secret_dev)); for (i, share) in shares.as_ref().iter().enumerate() { let share_num = i + 1; println!( @@ -666,51 +685,62 @@ fn main() -> Result<()> { make_dir(&args.state)?; match args.command { - Command::Ca { command } => match command { - CaCommand::Initialize { - key_spec, - pkcs11_path, - } => { - let _ = initialize_all_ca( - &key_spec, - &pkcs11_path, - &args.state, - &args.output, - )?; - Ok(()) - } - CaCommand::Sign { csr_spec } => { - let cas = load_all_ca(&args.state)?; - sign_all( - &cas, - &csr_spec, - &args.state, - &args.output, - args.transport, - ) + Command::Ca { + auth_method, + command, + } => { + // The CA modules pulls the password out of the environment. If + // ENV_PASSWORD isn't set the caller will be challenged. + let passwd_reader = secret_reader::get_passwd_reader(auth_method); + let password = passwd_reader.read(PASSWD_PROMPT)?; + std::env::set_var(ENV_PASSWORD, password.deref()); + + match command { + CaCommand::Initialize { + key_spec, + pkcs11_path, + } => { + let _ = initialize_all_ca( + &key_spec, + &pkcs11_path, + &args.state, + &args.output, + )?; + Ok(()) + } + CaCommand::Sign { csr_spec } => { + let cas = load_all_ca(&args.state)?; + sign_all( + &cas, + &csr_spec, + &args.state, + &args.output, + args.transport, + ) + } } - }, + } Command::Hsm { auth_id, command, no_backup, } => { - let passwd = get_passwd(auth_id, &command)?; - let auth_id = get_auth_id(auth_id, &command); - let mut hsm = Hsm::new( - auth_id, - &passwd, - &args.output, - &args.state, - !no_backup, - args.transport, - )?; - match command { HsmCommand::Initialize { - print_dev, passwd_challenge, + ref secret_method, + ref secret_dev, } => { + let passwd = Zeroizing::new("password".to_string()); + let mut hsm = Hsm::new( + 1, + &passwd, + &args.output, + &args.state, + !no_backup, + args.transport, + )?; + debug!("Initialize"); let wrap = BackupKey::from_rng(&mut hsm)?; let (shares, verifier) = wrap.split(&mut hsm)?; @@ -732,11 +762,13 @@ fn main() -> Result<()> { custodian is present in front of the printer.\n\n\ Press enter to begin the key share recording process ...", LIMIT, - print_dev.display(), + secret_dev.display(), ); - let secret_writer = - PrinterSecretWriter::new(Some(&print_dev)); + let secret_writer = secret_writer::get_writer( + *secret_method, + Some(secret_dev), + ); for (i, share) in shares.as_ref().iter().enumerate() { let share_num = i + 1; println!( @@ -766,19 +798,50 @@ fn main() -> Result<()> { get_new_passwd(Some(&mut hsm))? }; - let secret_writer = - PrinterSecretWriter::new(Some(&print_dev)); secret_writer.password(&passwd_new)?; hsm.import_backup_key(wrap)?; hsm.dump_attest_cert::(None)?; hsm.replace_default_auth(&passwd_new) } - HsmCommand::Generate { key_spec } => hsm.generate(&key_spec), - HsmCommand::Restore { backups, verifier } => { + HsmCommand::Generate { + ref auth_method, + ref key_spec, + } => { + let passwd = get_passwd(auth_id, auth_method, &command)?; + let auth_id = get_auth_id(auth_id, &command); + let hsm = Hsm::new( + auth_id, + &passwd, + &args.output, + &args.state, + !no_backup, + args.transport, + )?; + + hsm.generate(key_spec) + } + HsmCommand::Restore { + ref backups, + ref share_method, + ref verifier, + } => { + let passwd = Zeroizing::new("password".to_string()); + let mut hsm = Hsm::new( + 1, + &passwd, + &args.output, + &args.state, + !no_backup, + args.transport, + )?; + let verifier = fs::read_to_string(verifier)?; let verifier: Verifier = serde_json::from_str(&verifier)?; - let share_itr = StdioShareReader::new(verifier); + let share_itr = secret_reader::get_share_reader( + *share_method, + verifier, + ); let mut shares: Zeroizing> = Zeroizing::new(Vec::new()); @@ -795,20 +858,35 @@ fn main() -> Result<()> { info!("Deleting default authentication key"); oks::hsm::delete(&hsm.client, 1, Type::AuthenticationKey) } - HsmCommand::SerialNumber => oks::hsm::dump_sn(&hsm.client), + HsmCommand::SerialNumber { ref auth_method } => { + let passwd = get_passwd(auth_id, auth_method, &command)?; + let auth_id = get_auth_id(auth_id, &command); + let hsm = Hsm::new( + auth_id, + &passwd, + &args.output, + &args.state, + !no_backup, + args.transport, + )?; + + oks::hsm::dump_sn(&hsm.client) + } } } Command::Ceremony { ref csr_spec, ref key_spec, ref pkcs11_path, - ref print_dev, + ref secret_method, + ref secret_dev, passwd_challenge, } => do_ceremony( csr_spec, key_spec, pkcs11_path, - print_dev, + secret_method, + secret_dev, passwd_challenge, &args, ), diff --git a/src/secret_reader.rs b/src/secret_reader.rs index ba9c135..858c9fa 100644 --- a/src/secret_reader.rs +++ b/src/secret_reader.rs @@ -3,7 +3,9 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. use anyhow::Result; +use clap::{builder::ArgPredicate, ValueEnum}; use std::{ + ffi::OsStr, io::{self, Read, Write}, ops::Deref, }; @@ -11,15 +13,62 @@ use zeroize::Zeroizing; use crate::backup::{Share, Verifier}; +#[derive(ValueEnum, Copy, Clone, Debug, Default, PartialEq)] +pub enum SecretInput { + #[default] + Stdio, +} + +impl From for ArgPredicate { + fn from(val: SecretInput) -> Self { + let rep = match val { + SecretInput::Stdio => SecretInput::Stdio.into(), + }; + ArgPredicate::Equals(OsStr::new(rep).into()) + } +} + +impl From for &str { + fn from(val: SecretInput) -> &'static str { + match val { + SecretInput::Stdio => "stdio", + } + } +} + +pub trait PasswordReader { + fn read(&self, prompt: &str) -> Result>; +} + +pub fn get_passwd_reader(kind: SecretInput) -> Box { + let r = match kind { + SecretInput::Stdio => StdioPasswordReader {}, + }; + Box::new(r) +} + #[derive(Default)] pub struct StdioPasswordReader {} -impl StdioPasswordReader { - pub fn read(&self, prompt: &str) -> Result> { +impl PasswordReader for StdioPasswordReader { + fn read(&self, prompt: &str) -> Result> { Ok(Zeroizing::new(rpassword::prompt_password(prompt)?)) } } +pub fn get_share_reader( + kind: SecretInput, + verifier: Verifier, +) -> Box>>> { + let r = match kind { + SecretInput::Stdio => StdioShareReader::new(verifier), + }; + Box::new(r) +} + +// ShareReader require a verifier. We separate ShareReaders from from +// PasswordReaders because we need to create PasswordReaders in +// situations when we don't have a reader. pub struct StdioShareReader { verifier: Verifier, } diff --git a/src/secret_writer.rs b/src/secret_writer.rs index 94afe7a..31007ed 100644 --- a/src/secret_writer.rs +++ b/src/secret_writer.rs @@ -3,8 +3,10 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. use anyhow::Result; +use clap::{builder::ArgPredicate, ValueEnum}; use hex::ToHex; use std::{ + ffi::OsStr, fs::{File, OpenOptions}, io::Write, path::{Path, PathBuf}, @@ -31,6 +33,49 @@ const LF: u8 = 0x0a; const FF: u8 = 0x0c; const CR: u8 = 0x0d; +#[derive(ValueEnum, Clone, Copy, Debug, Default, PartialEq)] +pub enum SecretOutput { + #[default] + Printer, +} + +impl From for ArgPredicate { + fn from(val: SecretOutput) -> Self { + let rep = match val { + SecretOutput::Printer => SecretOutput::Printer.into(), + }; + ArgPredicate::Equals(OsStr::new(rep).into()) + } +} + +impl From for &str { + fn from(val: SecretOutput) -> Self { + match val { + SecretOutput::Printer => "printer", + } + } +} + +pub fn get_writer>( + kind: SecretOutput, + secret_dev: Option

, +) -> Box { + let w = match kind { + SecretOutput::Printer => PrinterSecretWriter::new(secret_dev), + }; + Box::new(w) +} + +pub trait SecretWriter { + fn password(&self, password: &Zeroizing) -> Result<()>; + fn share( + &self, + index: usize, + limit: usize, + share: &Zeroizing, + ) -> Result<()>; +} + /// This type exports secrets by writing them to a printer. /// This has only been tested with an Epson ESC/P. pub struct PrinterSecretWriter { @@ -46,8 +91,10 @@ impl PrinterSecretWriter { Self { device } } +} - pub fn password(&self, password: &Zeroizing) -> Result<()> { +impl SecretWriter for PrinterSecretWriter { + fn password(&self, password: &Zeroizing) -> Result<()> { println!( "\nWARNING: The HSM authentication password has been created and stored in\n\ the YubiHSM. It will now be printed to {}.\n\ @@ -103,7 +150,7 @@ impl PrinterSecretWriter { Ok(()) } - pub fn share( + fn share( &self, index: usize, limit: usize, From eb0d4a77773cced61e1d1ffc7ebbfe0d5cddf272 Mon Sep 17 00:00:00 2001 From: Philip Tricca Date: Wed, 4 Dec 2024 14:15:00 -0800 Subject: [PATCH 2/5] Add Cdr and Cdw types, implement CdrPasswordReader and CdrShareReader NOTE: The Cdw type is still unused --- src/cdrw.rs | 346 +++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + src/main.rs | 34 +++-- src/secret_reader.rs | 150 +++++++++++++++++-- 4 files changed, 501 insertions(+), 30 deletions(-) create mode 100644 src/cdrw.rs diff --git a/src/cdrw.rs b/src/cdrw.rs new file mode 100644 index 0000000..a9564f7 --- /dev/null +++ b/src/cdrw.rs @@ -0,0 +1,346 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use anyhow::{Context, Result}; +use log::{debug, warn}; +use std::{ + fs, + path::{Path, PathBuf}, + process::Command, +}; +use tempfile::{tempdir, TempDir}; +use thiserror::Error; +use zeroize::Zeroizing; + +pub const DEFAULT_CDRW_DEV: &str = "/dev/cdrom"; + +#[derive(Debug, Error)] +pub enum CdrwError { + #[error("The device provided isn't a block dev or a regular file.")] + BadDevice, + + #[error("Source directory is neither a file nor a directory.")] + BadSrc, + + #[error("Failed to burn tmpdir to CDR device.")] + BurnFail, + + #[error("Failed to eject Cdr.")] + EjectFail, + + #[error("Unable to get next available loopback device.")] + GetLoopback, + + #[error("Failed to make ISO from state directory.")] + IsoFail, + + #[error("Failed to mount Cdr.")] + MountFail, +} + +pub struct Cdr { + device: PathBuf, + tmpdir: TempDir, + loopback: Option, +} + +impl Cdr { + pub fn new>(device: Option

) -> Result { + let device = match device { + Some(s) => PathBuf::from(s.as_ref()), + None => PathBuf::from(DEFAULT_CDRW_DEV), + }; + Ok(Self { + device, + tmpdir: tempdir()?, + loopback: None, + }) + } + + pub fn eject(&self) -> Result<()> { + let mut cmd = Command::new("eject"); + let output = cmd.arg(&self.device).output().with_context(|| { + format!("failed to run the \"eject\" command: \"{:?}\"", cmd) + })?; + + if !output.status.success() { + warn!("command failed with status: {}", output.status); + warn!("stderr: \"{}\"", String::from_utf8_lossy(&output.stderr)); + return Err(CdrwError::EjectFail.into()); + } + + Ok(()) + } + + pub fn mount(&mut self) -> Result<()> { + use std::os::unix::fs::FileTypeExt; + // if self.device is a regular file assume it's an iso ... check + // suffix too? + // else if it's a device then just mount it + // otherwise fail + let file_type = self.device.metadata()?.file_type(); + if file_type.is_file() { + // if we've been givn an ISO we need to setup a loopback device + // checkout udisksctl? + let mut cmd = Command::new("losetup"); + let output = cmd + .arg("-f") + .output() + .with_context(|| "unable to execute \"losetup\"")?; + + debug!("executing command: \"{:#?}\"", cmd); + + if !output.status.success() { + warn!("command failed with status: {}", output.status); + warn!( + "stderr: \"{}\"", + String::from_utf8_lossy(&output.stderr) + ); + return Err(CdrwError::GetLoopback.into()); + } + // get path to the loopback device from `losetup` stdout + let loop_dev = + String::from(String::from_utf8(output.stdout)?.trim()); + debug!("got loopback device: {}", loop_dev); + let loop_dev = PathBuf::from(loop_dev); + + let mut cmd = Command::new("losetup"); + let output = cmd + .arg(&loop_dev) + .arg(&self.device) + .output() + .with_context(|| "failed to execute \"losetup\"")?; + + debug!("executing command: \"{:#?}\"", cmd); + if !output.status.success() { + warn!("command failed with status: {}", output.status); + warn!( + "stderr: \"{}\"", + String::from_utf8_lossy(&output.stderr) + ); + return Err(CdrwError::GetLoopback.into()); + } + + self._mount(&loop_dev)?; + self.loopback = Some(loop_dev); + } else if file_type.is_block_device() { + self._mount(&self.device)?; + } else { + return Err(CdrwError::BadDevice.into()); + } + + Ok(()) + } + + pub fn read(&self, name: &str) -> Result> { + let path = self.tmpdir.as_ref().join(name); + debug!("reading data from {}", path.display()); + + fs::read(&path).with_context(|| { + format!("failed to read file: {} from Cdr", path.display()) + }) + } + + // TODO: be resilient to device already mounted ... + // iterate over Process::mountinfo? + fn _mount>(&self, device: &P) -> Result<()> { + let mut cmd = Command::new("mount"); + let output = cmd + .arg(device.as_ref()) + .arg(self.tmpdir.as_ref()) + .output() + .with_context(|| { + format!( + "failed to mount \"{}\" at \"{}\"", + device.as_ref().display(), + self.tmpdir.as_ref().display() + ) + })?; + + if !output.status.success() { + warn!("command failed with status: {}", output.status); + warn!("stderr: \"{}\"", String::from_utf8_lossy(&output.stderr)); + return Err(CdrwError::MountFail.into()); + } + + Ok(()) + } + + // do this in `Drop`? + pub fn teardown(&self) { + // unmount self.tmpdir + let mut cmd = Command::new("umount"); + // TODO: clone + let output = cmd + .arg(self.device.clone()) + .arg(self.tmpdir.as_ref()) + .output(); + let output = match output { + Ok(o) => o, + _ => { + warn!( + "failed to unmount \"{}\"", + self.tmpdir.as_ref().display() + ); + return; + } + }; + + if !output.status.success() { + warn!("command failed with status: {}", output.status); + warn!("stderr: \"{}\"", String::from_utf8_lossy(&output.stderr)); + return; + } + + if self.loopback.is_some() { + let loopback = self.loopback.clone().unwrap(); + let mut cmd = Command::new("losetup"); + let output = cmd.arg("-d").arg(&loopback).output(); + + let output = match output { + Ok(o) => o, + _ => { + warn!( + "failed to destroy loopback device {}", + loopback.display() + ); + return; + } + }; + + if !output.status.success() { + warn!("command failed with status: {}", output.status); + warn!( + "stderr: \"{}\"", + String::from_utf8_lossy(&output.stderr) + ); + } + } + } +} + +pub struct Cdw { + tmp: TempDir, + device: PathBuf, +} + +impl Cdw { + // If `device` is `None` then we will only create an iso and return the + // bytes. + pub fn new(device: Option) -> Result { + let device = device.unwrap_or_else(|| { + // the error type return is infallible + PathBuf::from(DEFAULT_CDRW_DEV) + }); + Ok(Self { + device, + tmp: tempdir()?, + }) + } + + pub fn add>(&self, src: &P) -> Result<()> { + let name = src.as_ref().file_name().ok_or(CdrwError::BadSrc)?; + let dst = self.tmp.path().join(name); + + let _ = fs::copy(src, &dst).context(format!( + "Failed to copy source \"{}\" to destination \"{}\"", + src.as_ref().display(), + dst.display() + ))?; + Ok(()) + } + + pub fn write_password(&self, data: &Zeroizing) -> Result<()> { + let path = self.tmp.as_ref().join("password"); + debug!( + "Writing password: {} to: {}", + as AsRef>::as_ref(data), + path.display() + ); + + Ok(fs::write(path, data)?) + } + + pub fn write_share(&self, data: &[u8]) -> Result<()> { + let path = self.tmp.as_ref().join("share"); + debug!("Writing share: {:?} to: {}", data, path.display()); + + Ok(fs::write(path, data)?) + } + + pub fn to_iso>(&self, path: P) -> Result<()> { + let mut cmd = Command::new("mkisofs"); + let output = cmd + .arg("-r") + .arg("-iso-level") + .arg("4") + .arg("-o") + .arg(path.as_ref()) + .arg(self.tmp.as_ref()) + .output() + .with_context(|| { + format!( + "failed to create state ISO at \"{}\"", + self.tmp.as_ref().display() + ) + })?; + + if !output.status.success() { + warn!("command failed with status: {}", output.status); + warn!("stderr: \"{}\"", String::from_utf8_lossy(&output.stderr)); + return Err(CdrwError::IsoFail.into()); + } + + Ok(()) + } + + /// Burn data to CD & eject disk when done. + pub fn burn(self) -> Result<()> { + use tempfile::NamedTempFile; + + let iso = NamedTempFile::new()?; + self.to_iso(&iso)?; + + let mut cmd = Command::new("cdrecord"); + let output = cmd + .arg("-eject") + .arg("-data") + .arg(iso.path()) + .arg(format!("dev={}", self.device.display())) + .arg("gracetime=0") + .arg("timeout=1000") + .output() + .with_context(|| { + format!( + "failed to create ISO from \"{}\" at \"{}\"", + self.tmp.as_ref().display(), + self.tmp.as_ref().display() + ) + })?; + + if !output.status.success() { + warn!("command failed with status: {}", output.status); + warn!("stderr: \"{}\"", String::from_utf8_lossy(&output.stderr)); + return Err(CdrwError::BurnFail.into()); + } + + Ok(()) + } + + /// Eject / open CD device. + pub fn eject(&self) -> Result<()> { + let mut cmd = Command::new("eject"); + let output = cmd.arg(&self.device).output().with_context(|| { + format!("failed to eject CD device \"{}\"", self.device.display()) + })?; + + if !output.status.success() { + warn!("command failed with status: {}", output.status); + warn!("stderr: \"{}\"", String::from_utf8_lossy(&output.stderr)); + return Err(CdrwError::EjectFail.into()); + } + + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs index bad68dc..65e0c84 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,7 @@ pub mod alphabet; pub mod backup; pub mod ca; +pub mod cdrw; pub mod config; pub mod hsm; pub mod secret_reader; diff --git a/src/main.rs b/src/main.rs index 3d61a9a..98e20a6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,7 +25,9 @@ use oks::{ ENV_NEW_PASSWORD, ENV_PASSWORD, KEYSPEC_EXT, }, hsm::Hsm, - secret_reader::{self, PasswordReader, SecretInput, StdioPasswordReader}, + secret_reader::{ + self, PasswordReader, SecretInputArg, StdioPasswordReader, + }, secret_writer::{self, SecretOutput, DEFAULT_PRINT_DEV}, util, }; @@ -72,8 +74,8 @@ struct Args { #[derive(Subcommand, Debug, PartialEq)] enum Command { Ca { - #[clap(long, env)] - auth_method: SecretInput, + #[clap(flatten)] + auth_method: SecretInputArg, #[command(subcommand)] command: CaCommand, @@ -159,8 +161,8 @@ enum CaCommand { enum HsmCommand { /// Generate keys in YubiHSM from specification. Generate { - #[clap(long, env)] - auth_method: SecretInput, + #[clap(flatten)] + auth_method: SecretInputArg, #[clap(long, env, default_value = "input")] key_spec: PathBuf, @@ -188,8 +190,8 @@ enum HsmCommand { #[clap(long, env, default_value = "input")] backups: PathBuf, - #[clap(long, env)] - share_method: SecretInput, + #[clap(flatten)] + share_method: SecretInputArg, #[clap(long, env, default_value = "input/verifier.json")] verifier: PathBuf, @@ -197,8 +199,8 @@ enum HsmCommand { /// Get serial number from YubiHSM and dump to console. SerialNumber { - #[clap(long, env)] - auth_method: SecretInput, + #[clap(flatten)] + auth_method: SecretInputArg, }, } @@ -243,13 +245,14 @@ fn get_auth_id(auth_id: Option, command: &HsmCommand) -> Id { /// the user with a password prompt. fn get_passwd( auth_id: Option, - auth_method: &SecretInput, + auth_method: &SecretInputArg, command: &HsmCommand, ) -> Result> { let passwd = match env::var(ENV_PASSWORD).ok() { Some(s) => Zeroizing::new(s), None => { - let passwd_reader = secret_reader::get_passwd_reader(*auth_method); + let mut passwd_reader = + secret_reader::get_passwd_reader(auth_method)?; if auth_id.is_some() { // if auth_id was set by the caller but not the password we @@ -295,7 +298,7 @@ fn get_new_passwd(hsm: Option<&mut Hsm>) -> Result> { } // last option: challenge the caller None => { - let passwd_reader = StdioPasswordReader::default(); + let mut passwd_reader = StdioPasswordReader::default(); loop { let password = passwd_reader.read(PASSWD_NEW)?; let password2 = passwd_reader.read(PASSWD_NEW_2)?; @@ -691,7 +694,8 @@ fn main() -> Result<()> { } => { // The CA modules pulls the password out of the environment. If // ENV_PASSWORD isn't set the caller will be challenged. - let passwd_reader = secret_reader::get_passwd_reader(auth_method); + let mut passwd_reader = + secret_reader::get_passwd_reader(&auth_method)?; let password = passwd_reader.read(PASSWD_PROMPT)?; std::env::set_var(ENV_PASSWORD, password.deref()); @@ -839,9 +843,9 @@ fn main() -> Result<()> { let verifier = fs::read_to_string(verifier)?; let verifier: Verifier = serde_json::from_str(&verifier)?; let share_itr = secret_reader::get_share_reader( - *share_method, + share_method, verifier, - ); + )?; let mut shares: Zeroizing> = Zeroizing::new(Vec::new()); diff --git a/src/secret_reader.rs b/src/secret_reader.rs index 858c9fa..c7eb117 100644 --- a/src/secret_reader.rs +++ b/src/secret_reader.rs @@ -3,18 +3,33 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. use anyhow::Result; -use clap::{builder::ArgPredicate, ValueEnum}; +use clap::{builder::ArgPredicate, Args, ValueEnum}; +use log::debug; use std::{ ffi::OsStr, io::{self, Read, Write}, ops::Deref, + path::PathBuf, }; use zeroize::Zeroizing; -use crate::backup::{Share, Verifier}; +use crate::{ + backup::{Share, Verifier}, + cdrw::Cdr, +}; + +#[derive(Args, Clone, Debug, Default, PartialEq)] +pub struct SecretInputArg { + #[clap(long, env)] + auth_method: SecretInput, + + #[clap(long, env)] + auth_dev: Option, +} #[derive(ValueEnum, Copy, Clone, Debug, Default, PartialEq)] pub enum SecretInput { + Cdr, #[default] Stdio, } @@ -22,6 +37,7 @@ pub enum SecretInput { impl From for ArgPredicate { fn from(val: SecretInput) -> Self { let rep = match val { + SecretInput::Cdr => SecretInput::Cdr.into(), SecretInput::Stdio => SecretInput::Stdio.into(), }; ArgPredicate::Equals(OsStr::new(rep).into()) @@ -31,39 +47,48 @@ impl From for ArgPredicate { impl From for &str { fn from(val: SecretInput) -> &'static str { match val { + SecretInput::Cdr => "cdr", SecretInput::Stdio => "stdio", } } } pub trait PasswordReader { - fn read(&self, prompt: &str) -> Result>; + fn read(&mut self, prompt: &str) -> Result>; } -pub fn get_passwd_reader(kind: SecretInput) -> Box { - let r = match kind { - SecretInput::Stdio => StdioPasswordReader {}, - }; - Box::new(r) +pub fn get_passwd_reader( + input: &SecretInputArg, +) -> Result> { + Ok(match input.auth_method { + SecretInput::Stdio => Box::new(StdioPasswordReader {}), + SecretInput::Cdr => { + let cdr = Cdr::new(input.auth_dev.as_ref())?; + Box::new(CdrPasswordReader::new(cdr)) + } + }) } #[derive(Default)] pub struct StdioPasswordReader {} impl PasswordReader for StdioPasswordReader { - fn read(&self, prompt: &str) -> Result> { + fn read(&mut self, prompt: &str) -> Result> { Ok(Zeroizing::new(rpassword::prompt_password(prompt)?)) } } pub fn get_share_reader( - kind: SecretInput, + input: &SecretInputArg, verifier: Verifier, -) -> Box>>> { - let r = match kind { - SecretInput::Stdio => StdioShareReader::new(verifier), - }; - Box::new(r) +) -> Result>>>> { + Ok(match input.auth_method { + SecretInput::Stdio => Box::new(StdioShareReader::new(verifier)), + SecretInput::Cdr => { + let cdr = Cdr::new(input.auth_dev.as_ref())?; + Box::new(CdrShareReader::new(cdr, verifier)) + } + }) } // ShareReader require a verifier. We separate ShareReaders from from @@ -177,6 +202,101 @@ impl Iterator for StdioShareReader { } } +pub struct CdrPasswordReader { + cdr: Cdr, +} + +impl CdrPasswordReader { + pub fn new(cdr: Cdr) -> Self { + Self { cdr } + } +} + +impl PasswordReader for CdrPasswordReader { + // TODO: figure out user interaction / prompt stuff + // TODO: if this were to consume `self` we may be able to make + // `Cdr::teardown` do the same ... + fn read(&mut self, _prompt: &str) -> Result> { + self.cdr.mount()?; + + let password = self.cdr.read("password")?; + + // Passwords are utf8 and `String::from_utf8` explicitly does *not* + // copy the Vec. + let password = Zeroizing::new(String::from_utf8(password)?); + debug!("read password: {:?}", password.deref()); + self.cdr.teardown(); + + Ok(password) + } +} + +pub struct CdrShareReader { + cdr: Cdr, + verifier: Verifier, +} + +impl CdrShareReader { + pub fn new(cdr: Cdr, verifier: Verifier) -> Self { + Self { cdr, verifier } + } +} + +impl Iterator for CdrShareReader { + type Item = Result>; + + fn next(&mut self) -> Option { + match self.cdr.eject() { + Ok(()) => (), + Err(e) => return Some(Err(e)), + } + + print!( + "Place keyshare CD in the drive, close the drive, then press \n\ + any key to continue: " + ); + match io::stdout().flush() { + Ok(()) => (), + Err(e) => return Some(Err(e.into())), + } + // wait for user input + match io::stdin().read_exact(&mut [0u8]) { + Ok(_) => (), + Err(e) => return Some(Err(e.into())), + }; + + // TODO: retry loop + // a drive tested errors out very quickly if the drive is still + // reading the disk, another will just block until data is ready + // ¯\_(ツ)_/¯ + match self.cdr.mount() { + Ok(()) => (), + Err(e) => return Some(Err(e)), + } + let share = match self.cdr.read("share") { + Ok(b) => b, + Err(e) => return Some(Err(e)), + }; + println!("\nOK"); + + let share = match Share::try_from(share.deref()) { + Ok(s) => Zeroizing::new(s), + Err(e) => return Some(Err(e.into())), + }; + + match verify(&self.verifier, &share) { + Ok(b) => { + if b { + Some(Ok(share)) + } else { + Some(Err(anyhow::anyhow!("verification failed"))) + } + } + Err(e) => Some(Err(e)), + } + } +} + fn verify(verifier: &Verifier, share: &Zeroizing) -> Result { if verifier.verify(share.deref()) { print!("\nShare verified!\n\nPress any key to continue ..."); From 03f5840e1ddb497d9fd4bd6d153a05eb15a1124b Mon Sep 17 00:00:00 2001 From: Philip Tricca Date: Fri, 6 Dec 2024 12:16:05 -0800 Subject: [PATCH 3/5] wip: Add SecretWriter for Cdw. --- src/cdrw.rs | 10 ++++---- src/main.rs | 4 +-- src/secret_writer.rs | 60 +++++++++++++++++++++++++++++++++++++++----- 3 files changed, 61 insertions(+), 13 deletions(-) diff --git a/src/cdrw.rs b/src/cdrw.rs index a9564f7..352a7b7 100644 --- a/src/cdrw.rs +++ b/src/cdrw.rs @@ -228,11 +228,11 @@ pub struct Cdw { impl Cdw { // If `device` is `None` then we will only create an iso and return the // bytes. - pub fn new(device: Option) -> Result { - let device = device.unwrap_or_else(|| { - // the error type return is infallible - PathBuf::from(DEFAULT_CDRW_DEV) - }); + pub fn new>(device: Option

) -> Result { + let device = match device { + Some(s) => PathBuf::from(s.as_ref()), + None => PathBuf::from(DEFAULT_CDRW_DEV), + }; Ok(Self { device, tmp: tempdir()?, diff --git a/src/main.rs b/src/main.rs index 98e20a6..92b88cb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -363,7 +363,7 @@ fn do_ceremony>( ); let secret_writer = - secret_writer::get_writer(*secret_method, Some(secret_dev)); + secret_writer::get_writer(*secret_method, Some(secret_dev))?; for (i, share) in shares.as_ref().iter().enumerate() { let share_num = i + 1; println!( @@ -772,7 +772,7 @@ fn main() -> Result<()> { let secret_writer = secret_writer::get_writer( *secret_method, Some(secret_dev), - ); + )?; for (i, share) in shares.as_ref().iter().enumerate() { let share_num = i + 1; println!( diff --git a/src/secret_writer.rs b/src/secret_writer.rs index 31007ed..09038e8 100644 --- a/src/secret_writer.rs +++ b/src/secret_writer.rs @@ -13,7 +13,7 @@ use std::{ }; use zeroize::Zeroizing; -use crate::{backup::Share, util}; +use crate::{backup::Share, cdrw::Cdw, util}; pub const DEFAULT_PRINT_DEV: &str = "/dev/usb/lp0"; @@ -35,6 +35,7 @@ const CR: u8 = 0x0d; #[derive(ValueEnum, Clone, Copy, Debug, Default, PartialEq)] pub enum SecretOutput { + Cdw, #[default] Printer, } @@ -42,6 +43,7 @@ pub enum SecretOutput { impl From for ArgPredicate { fn from(val: SecretOutput) -> Self { let rep = match val { + SecretOutput::Cdw => SecretOutput::Cdw.into(), SecretOutput::Printer => SecretOutput::Printer.into(), }; ArgPredicate::Equals(OsStr::new(rep).into()) @@ -51,6 +53,7 @@ impl From for ArgPredicate { impl From for &str { fn from(val: SecretOutput) -> Self { match val { + SecretOutput::Cdw => "cdw", SecretOutput::Printer => "printer", } } @@ -59,11 +62,11 @@ impl From for &str { pub fn get_writer>( kind: SecretOutput, secret_dev: Option

, -) -> Box { - let w = match kind { - SecretOutput::Printer => PrinterSecretWriter::new(secret_dev), - }; - Box::new(w) +) -> Result> { + Ok(match kind { + SecretOutput::Cdw => Box::new(CdwSecretWriter::new(secret_dev)), + SecretOutput::Printer => Box::new(PrinterSecretWriter::new(secret_dev)), + }) } pub trait SecretWriter { @@ -244,3 +247,48 @@ fn print_whitespace_notice( Ok(()) } + +pub struct CdwSecretWriter { + device: Option, +} + +impl CdwSecretWriter { + pub fn new>(device: Option

) -> Self { + let device = device.map(|p| p.as_ref().to_path_buf()); + + Self { device } + } +} + +impl SecretWriter for CdwSecretWriter { + fn password(&self, password: &Zeroizing) -> Result<()> { + println!( + "\nWARNING: The HSM authentication password has been created and stored in\n\ + the YubiHSM. It will now be written to CDR media.\n\n\ + Press enter to print the HSM password ...", + ); + + util::wait_for_line()?; + + let cdw = Cdw::new(self.device.as_ref())?; + + cdw.write_password(password)?; + cdw.burn()?; + + println!("Remove CD from drive then press enter."); + util::wait_for_line() + } + fn share( + &self, + _index: usize, + _limit: usize, + share: &Zeroizing, + ) -> Result<()> { + let cdw = Cdw::new(self.device.as_ref())?; + + cdw.write_share(share.as_ref())?; + cdw.burn()?; + + Ok(()) + } +} From fcb2c31a01f085c92d4709610992a7f8a58bdd45 Mon Sep 17 00:00:00 2001 From: Philip Tricca Date: Tue, 10 Dec 2024 14:32:48 -0800 Subject: [PATCH 4/5] Add Isos to SecretWriters. This has me thinking that the Cdw type should be broken up into one that generates Isos and one that does the burning? --- src/secret_writer.rs | 48 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/src/secret_writer.rs b/src/secret_writer.rs index 09038e8..634e318 100644 --- a/src/secret_writer.rs +++ b/src/secret_writer.rs @@ -2,10 +2,11 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -use anyhow::Result; +use anyhow::{Context, Result}; use clap::{builder::ArgPredicate, ValueEnum}; use hex::ToHex; use std::{ + env, ffi::OsStr, fs::{File, OpenOptions}, io::Write, @@ -36,6 +37,7 @@ const CR: u8 = 0x0d; #[derive(ValueEnum, Clone, Copy, Debug, Default, PartialEq)] pub enum SecretOutput { Cdw, + Iso, #[default] Printer, } @@ -44,6 +46,7 @@ impl From for ArgPredicate { fn from(val: SecretOutput) -> Self { let rep = match val { SecretOutput::Cdw => SecretOutput::Cdw.into(), + SecretOutput::Iso => SecretOutput::Iso.into(), SecretOutput::Printer => SecretOutput::Printer.into(), }; ArgPredicate::Equals(OsStr::new(rep).into()) @@ -54,6 +57,7 @@ impl From for &str { fn from(val: SecretOutput) -> Self { match val { SecretOutput::Cdw => "cdw", + SecretOutput::Iso => "iso", SecretOutput::Printer => "printer", } } @@ -65,6 +69,10 @@ pub fn get_writer>( ) -> Result> { Ok(match kind { SecretOutput::Cdw => Box::new(CdwSecretWriter::new(secret_dev)), + SecretOutput::Iso => Box::new( + IsoSecretWriter::new(secret_dev) + .context("Failed to create IsoSecretWriter")?, + ), SecretOutput::Printer => Box::new(PrinterSecretWriter::new(secret_dev)), }) } @@ -292,3 +300,41 @@ impl SecretWriter for CdwSecretWriter { Ok(()) } } + +pub struct IsoSecretWriter { + output_dir: PathBuf, +} + +impl IsoSecretWriter { + pub fn new>(output_dir: Option

) -> Result { + let output_dir = match output_dir { + None => env::current_dir().context("Failed to get PWD")?, + Some(o) => o.as_ref().to_path_buf(), + }; + + Ok(Self { output_dir }) + } +} + +impl SecretWriter for IsoSecretWriter { + fn password(&self, password: &Zeroizing) -> Result<()> { + let cdw = Cdw::new::(None)?; + + cdw.write_password(password)?; + cdw.to_iso(self.output_dir.join("password.iso")) + } + fn share( + &self, + index: usize, + limit: usize, + share: &Zeroizing, + ) -> Result<()> { + let cdw = Cdw::new::(None)?; + + cdw.write_share(share.as_ref())?; + cdw.to_iso( + self.output_dir + .join(format!("share_{}-of-{}.iso", index, limit)), + ) + } +} From 9e07c5b524759a55b4a0bb51caeff2a57960f5df Mon Sep 17 00:00:00 2001 From: Philip Tricca Date: Tue, 10 Dec 2024 19:46:42 -0800 Subject: [PATCH 5/5] wip: Add SecretReader for Isos. --- Cargo.lock | 7 +++ Cargo.toml | 1 + src/secret_reader.rs | 115 ++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 121 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9e8fda7..b2a37a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -680,6 +680,12 @@ dependencies = [ "wasi 0.11.0+wasi-snapshot-preview1", ] +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + [[package]] name = "group" version = "0.12.1" @@ -1038,6 +1044,7 @@ dependencies = [ "clap", "env_logger", "fs_extra", + "glob", "hex", "log", "lpc55_areas", diff --git a/Cargo.toml b/Cargo.toml index 99df1c0..5f34e66 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,3 +37,4 @@ x509-cert = "0.2.5" yubihsm = { git = "https://github.com/oxidecomputer/yubihsm.rs", branch = "session-close", features = ["usb", "untested"] } zeroize = "1.8.1" zeroize_derive = "1.4.2" +glob = "0.3.1" diff --git a/src/secret_reader.rs b/src/secret_reader.rs index c7eb117..ed143d6 100644 --- a/src/secret_reader.rs +++ b/src/secret_reader.rs @@ -2,14 +2,16 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -use anyhow::Result; +use anyhow::{Context, Result}; use clap::{builder::ArgPredicate, Args, ValueEnum}; +use glob::Paths; use log::debug; use std::{ + env, ffi::OsStr, io::{self, Read, Write}, ops::Deref, - path::PathBuf, + path::{Path, PathBuf}, }; use zeroize::Zeroizing; @@ -30,6 +32,7 @@ pub struct SecretInputArg { #[derive(ValueEnum, Copy, Clone, Debug, Default, PartialEq)] pub enum SecretInput { Cdr, + Iso, #[default] Stdio, } @@ -38,6 +41,7 @@ impl From for ArgPredicate { fn from(val: SecretInput) -> Self { let rep = match val { SecretInput::Cdr => SecretInput::Cdr.into(), + SecretInput::Iso => SecretInput::Iso.into(), SecretInput::Stdio => SecretInput::Stdio.into(), }; ArgPredicate::Equals(OsStr::new(rep).into()) @@ -48,6 +52,7 @@ impl From for &str { fn from(val: SecretInput) -> &'static str { match val { SecretInput::Cdr => "cdr", + SecretInput::Iso => "iso", SecretInput::Stdio => "stdio", } } @@ -62,6 +67,10 @@ pub fn get_passwd_reader( ) -> Result> { Ok(match input.auth_method { SecretInput::Stdio => Box::new(StdioPasswordReader {}), + SecretInput::Iso => { + let cdr = Cdr::new(input.auth_dev.as_ref())?; + Box::new(IsoPasswordReader::new(cdr)) + } SecretInput::Cdr => { let cdr = Cdr::new(input.auth_dev.as_ref())?; Box::new(CdrPasswordReader::new(cdr)) @@ -88,6 +97,9 @@ pub fn get_share_reader( let cdr = Cdr::new(input.auth_dev.as_ref())?; Box::new(CdrShareReader::new(cdr, verifier)) } + SecretInput::Iso => { + Box::new(IsoShareReader::new(input.auth_dev.as_ref(), verifier)?) + } }) } @@ -297,6 +309,105 @@ impl Iterator for CdrShareReader { } } +struct IsoPasswordReader { + cdr: Cdr, +} + +impl IsoPasswordReader { + pub fn new(cdr: Cdr) -> Self { + Self { cdr } + } +} + +impl PasswordReader for IsoPasswordReader { + fn read(&mut self, _prompt: &str) -> Result> { + self.cdr.mount()?; + + let password = self.cdr.read("password")?; + let password = Zeroizing::new(String::from_utf8(password)?); + debug!("read password: {:?}", password.deref()); + self.cdr.teardown(); + Ok(password) + } +} + +struct IsoShareReader { + globs: Paths, + verifier: Verifier, +} + +const SHARE_ISO_GLOB: &str = "share_*-of-*.iso"; + +impl IsoShareReader { + // TODO: verifier + pub fn new>( + dir: Option

, + verifier: Verifier, + ) -> Result { + let dir = match dir { + None => env::current_dir().context("Failed to get PWD")?, + Some(d) => d.as_ref().to_path_buf(), + }; + + let globs = glob::glob( + dir.join(SHARE_ISO_GLOB) + .to_str() + .context("path can't be represented as an str")?, + ) + .context(format!("Invalid Glob: {}", SHARE_ISO_GLOB))?; + + Ok(Self { globs, verifier }) + } +} + +impl Iterator for IsoShareReader { + type Item = Result>; + + fn next(&mut self) -> Option { + let share_iso = match self.globs.next() { + None => { + debug!("no globs left"); + return None; + } + Some(r) => match r { + Ok(iso) => iso, + Err(e) => return Some(Err(e.into())), + }, + }; + + debug!("getting share from ISO: {}", share_iso.display()); + let mut cdr = match Cdr::new(Some(share_iso)) { + Err(e) => return Some(Err(e)), + Ok(c) => c, + }; + + if let Err(e) = cdr.mount() { + return Some(Err(e)); + } + + let share = match cdr.read("share") { + Err(e) => return Some(Err(e)), + Ok(s) => s, + }; + + let share = match Share::try_from(&share[..]) { + Ok(s) => Zeroizing::new(s), + Err(e) => return Some(Err(e.into())), + }; + + match verify(&self.verifier, &share) { + Ok(v) => { + if v { + Some(Ok(share)) + } else { + Some(Err(anyhow::anyhow!("verification failed"))) + } + } + Err(e) => Some(Err(e)), + } + } +} + fn verify(verifier: &Verifier, share: &Zeroizing) -> Result { if verifier.verify(share.deref()) { print!("\nShare verified!\n\nPress any key to continue ...");