Skip to content

Commit 2e2673b

Browse files
committed
Move code to get secrets into a module.
This refactor gets the code responsible for importing secrets from the user. This gets a significant amount of code driving interaction with the user out of the `hsm` module and into one dedicated to the purpose.
1 parent b4eccf9 commit 2e2673b

File tree

4 files changed

+228
-141
lines changed

4 files changed

+228
-141
lines changed

src/hsm.rs

Lines changed: 11 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ use static_assertions as sa;
1212
use std::collections::HashSet;
1313
use std::{
1414
fs,
15-
io::{self, Read, Write},
15+
io::{self, Write},
16+
ops::Deref,
1617
path::{Path, PathBuf},
1718
str::FromStr,
1819
};
@@ -40,7 +41,6 @@ const SEED_LEN: usize = 32;
4041
const KEY_LEN: usize = 32;
4142
const SHARE_LEN: usize = KEY_LEN + 1;
4243
const LABEL: &str = "backup";
43-
pub const VERIFIER_FILE: &str = "verifier.json";
4444

4545
pub const LIMIT: usize = 5;
4646
pub const THRESHOLD: usize = 3;
@@ -73,6 +73,8 @@ pub enum HsmError {
7373
SplitKeyFailed { e: vsss_rs::Error },
7474
#[error("your yubihms is broke")]
7575
Version,
76+
#[error("Not enough shares.")]
77+
NotEnoughShares,
7678
}
7779

7880
pub struct Alphabet {
@@ -381,119 +383,20 @@ impl Hsm {
381383
/// This function prompts the user to enter M of the N backup shares. It
382384
/// uses these shares to reconstitute the wrap key. This wrap key can then
383385
/// be used to restore previously backed up / export wrapped keys.
384-
pub fn restore_wrap(&self) -> Result<()> {
386+
/// This function prompts the user to enter M of the N backup shares. It
387+
/// uses these shares to reconstitute the wrap key. This wrap key can then
388+
/// be used to restore previously backed up / export wrapped keys.
389+
pub fn restore_wrap(&self, shares: Zeroizing<Vec<Share>>) -> Result<()> {
385390
info!("Restoring HSM from backup");
386-
info!("Restoring backup / wrap key from shares");
387-
// vector used to collect shares
388-
let mut shares: Vec<Share> = Vec::new();
389-
390-
// deserialize verifier:
391-
// verifier was serialized to output/verifier.json in the provisioning ceremony
392-
// it must be included in and deserialized from the ceremony inputs
393-
let verifier = self.out_dir.join(VERIFIER_FILE);
394-
let verifier = fs::read_to_string(verifier)?;
395-
let verifier: Verifier = serde_json::from_str(&verifier)?;
396-
397-
// get enough shares to recover backup key
398-
for _ in 1..=THRESHOLD {
399-
// attempt to get a single share until the custodian enters a
400-
// share that we can verify
401-
loop {
402-
// clear the screen, move cursor to (0,0), & prompt user
403-
print!("\x1B[2J\x1B[1;1H");
404-
print!("Enter share\n: ");
405-
io::stdout().flush()?;
406-
// get share from stdin
407-
let mut share = String::new();
408-
let share = match io::stdin().read_line(&mut share) {
409-
Ok(count) => match count {
410-
0 => {
411-
// Ctrl^D / EOF
412-
continue;
413-
}
414-
// 33 bytes -> 66 characters + 1 newline
415-
67 => share,
416-
_ => {
417-
print!(
418-
"\nexpected 67 characters, got {}.\n\n\
419-
Press any key to try again ...",
420-
share.len()
421-
);
422-
io::stdout().flush()?;
423-
424-
// wait for a keypress / 1 byte from stdin
425-
let _ = io::stdin().read(&mut [0u8]).unwrap();
426-
continue;
427-
}
428-
},
429-
Err(e) => {
430-
print!(
431-
"Error from `Stdin::read_line`: {}\n\n\
432-
Press any key to try again ...",
433-
e
434-
);
435-
io::stdout().flush()?;
436-
437-
// wait for a keypress / 1 byte from stdin
438-
let _ = io::stdin().read(&mut [0u8]).unwrap();
439-
continue;
440-
}
441-
};
442-
443-
// drop all whitespace from line entered, interpret it as a
444-
// hex string that we decode
445-
let share: String =
446-
share.chars().filter(|c| !c.is_whitespace()).collect();
447-
let share_vec = match hex::decode(share) {
448-
Ok(share) => share,
449-
Err(_) => {
450-
println!(
451-
"Failed to decode Share. The value entered isn't \
452-
a valid hex string: try again."
453-
);
454-
continue;
455-
}
456-
};
457391

458-
// construct a Share from the decoded hex string
459-
let share = match Share::try_from(&share_vec[..]) {
460-
Ok(share) => share,
461-
Err(_) => {
462-
println!(
463-
"Failed to convert share entered to Share type. \
464-
The value entered is the wrong length ... try \
465-
again."
466-
);
467-
continue;
468-
}
469-
};
470-
471-
if verifier.verify(&share) {
472-
// if we're going to switch from paper to CDs for key
473-
// share persistence this is the most obvious place to
474-
// put a keyshare on to a CD w/ lots of refactoring
475-
shares.push(share);
476-
print!(
477-
"\nShare verified!\n\nPress any key to continue ..."
478-
);
479-
io::stdout().flush()?;
480-
481-
// wait for a keypress / 1 byte from stdin
482-
let _ = io::stdin().read(&mut [0u8]).unwrap();
483-
break;
484-
} else {
485-
println!("Failed to verify share: try again");
486-
continue;
487-
}
488-
}
392+
if shares.len() < THRESHOLD {
393+
return Err(HsmError::NotEnoughShares.into());
489394
}
490395

491-
print!("\x1B[2J\x1B[1;1H");
492-
493396
let scalar = Feldman::<THRESHOLD, LIMIT>::combine_shares::<
494397
Scalar,
495398
SHARE_LEN,
496-
>(&shares)
399+
>(shares.deref())
497400
.map_err(|e| HsmError::CombineKeyFailed { e })?;
498401

499402
let nz_scalar = NonZeroScalar::from_repr(scalar.to_repr());

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@
55
pub mod ca;
66
pub mod config;
77
pub mod hsm;
8+
pub mod secret_reader;
89
pub mod secret_writer;
910
pub mod util;

src/main.rs

Lines changed: 65 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use log::{debug, error, info, LevelFilter};
99
use std::{
1010
collections::HashMap,
1111
env, fs,
12+
ops::{Deref, DerefMut},
1213
path::{Path, PathBuf},
1314
str::FromStr,
1415
};
@@ -21,15 +22,18 @@ use oks::{
2122
self, CsrSpec, DcsrSpec, KeySpec, Transport, CSRSPEC_EXT, DCSRSPEC_EXT,
2223
ENV_NEW_PASSWORD, ENV_PASSWORD, KEYSPEC_EXT,
2324
},
24-
hsm::{Hsm, LIMIT, VERIFIER_FILE},
25+
hsm::{Hsm, Share, Verifier, LIMIT, THRESHOLD},
26+
secret_reader::{StdioPasswordReader, StdioShareReader},
2527
secret_writer::{PrinterSecretWriter, DEFAULT_PRINT_DEV},
2628
util,
2729
};
2830

29-
const PASSWD_PROMPT: &str = "Enter new password: ";
30-
const PASSWD_PROMPT2: &str = "Enter password again to confirm: ";
31+
const PASSWD_PROMPT: &str = "Enter YubiHSM Password: ";
32+
const PASSWD_NEW: &str = "Enter new password: ";
33+
const PASSWD_NEW_2: &str = "Enter password again to confirm: ";
3134

3235
const GEN_PASSWD_LENGTH: usize = 16;
36+
const VERIFIER_FILE: &str = "verifier.json";
3337

3438
// when we write out signed certs to the file system this suffix is appended
3539
const CERT_SUFFIX: &str = "cert.pem";
@@ -163,7 +167,13 @@ enum HsmCommand {
163167
},
164168

165169
/// Restore a previously split aes256-ccm-wrap key
166-
Restore,
170+
Restore {
171+
#[clap(long, env, default_value = "input")]
172+
backups: PathBuf,
173+
174+
#[clap(long, env, default_value = "input/verifier.json")]
175+
verifier: PathBuf,
176+
},
167177

168178
/// Get serial number from YubiHSM and dump to console.
169179
SerialNumber,
@@ -200,7 +210,7 @@ fn get_auth_id(auth_id: Option<Id>, command: &HsmCommand) -> Id {
200210
print_dev: _,
201211
passwd_challenge: _,
202212
}
203-
| HsmCommand::Restore
213+
| HsmCommand::Restore { .. }
204214
| HsmCommand::SerialNumber => 1,
205215
// otherwise we assume the auth key that we create is
206216
// present: auth_id 2
@@ -211,14 +221,18 @@ fn get_auth_id(auth_id: Option<Id>, command: &HsmCommand) -> Id {
211221

212222
/// Get password either from environment, the YubiHSM2 default, or challenge
213223
/// the user with a password prompt.
214-
fn get_passwd(auth_id: Option<Id>, command: &HsmCommand) -> Result<String> {
215-
match env::var(ENV_PASSWORD).ok() {
216-
Some(s) => Ok(s),
224+
fn get_passwd(
225+
auth_id: Option<Id>,
226+
command: &HsmCommand,
227+
) -> Result<Zeroizing<String>> {
228+
let passwd = match env::var(ENV_PASSWORD).ok() {
229+
Some(s) => Zeroizing::new(s),
217230
None => {
231+
let passwd_reader = StdioPasswordReader::default();
218232
if auth_id.is_some() {
219233
// if auth_id was set by the caller but not the password we
220234
// prompt for the password
221-
Ok(rpassword::prompt_password("Enter YubiHSM Password: ")?)
235+
passwd_reader.read(PASSWD_PROMPT)?
222236
} else {
223237
match command {
224238
// if password isn't set, auth_id isn't set, and
@@ -229,47 +243,52 @@ fn get_passwd(auth_id: Option<Id>, command: &HsmCommand) -> Result<String> {
229243
print_dev: _,
230244
passwd_challenge: _,
231245
}
232-
| HsmCommand::Restore
233-
| HsmCommand::SerialNumber => Ok("password".to_string()),
246+
| HsmCommand::Restore { .. }
247+
| HsmCommand::SerialNumber => {
248+
Zeroizing::new("password".to_string())
249+
}
234250
// otherwise prompt the user for the password
235-
_ => Ok(rpassword::prompt_password(
236-
"Enter YubiHSM Password: ",
237-
)?),
251+
_ => passwd_reader.read(PASSWD_PROMPT)?,
238252
}
239253
}
240254
}
241-
}
255+
};
256+
257+
Ok(passwd)
242258
}
243259

244260
/// get a new password from the environment or by issuing a challenge the user
245261
fn get_new_passwd(hsm: Option<&Hsm>) -> Result<Zeroizing<String>> {
246-
match env::var(ENV_NEW_PASSWORD).ok() {
262+
let passwd = match env::var(ENV_NEW_PASSWORD).ok() {
247263
// prefer new password from env above all else
248264
Some(s) => {
249265
info!("got password from env");
250-
Ok(Zeroizing::new(s))
266+
Zeroizing::new(s)
251267
}
252268
None => match hsm {
253269
// use the HSM otherwise if available
254270
Some(hsm) => {
255271
info!("Generating random password");
256-
Ok(Zeroizing::new(hsm.rand_string(GEN_PASSWD_LENGTH)?))
272+
Zeroizing::new(hsm.rand_string(GEN_PASSWD_LENGTH)?)
257273
}
258274
// last option: challenge the caller
259-
None => loop {
260-
let password =
261-
Zeroizing::new(rpassword::prompt_password(PASSWD_PROMPT)?);
262-
let password2 =
263-
Zeroizing::new(rpassword::prompt_password(PASSWD_PROMPT2)?);
264-
if password != password2 {
265-
error!("the passwords entered do not match");
266-
} else {
267-
debug!("got the same password twice");
268-
return Ok(password);
275+
None => {
276+
let passwd_reader = StdioPasswordReader::default();
277+
loop {
278+
let password = passwd_reader.read(PASSWD_NEW)?;
279+
let password2 = passwd_reader.read(PASSWD_NEW_2)?;
280+
if password != password2 {
281+
error!("the passwords entered do not match");
282+
} else {
283+
debug!("got the same password twice");
284+
break password;
285+
}
269286
}
270-
},
287+
}
271288
},
272-
}
289+
};
290+
291+
Ok(passwd)
273292
}
274293

275294
/// Perform all operations that make up the ceremony for provisioning an
@@ -746,9 +765,22 @@ fn main() -> Result<()> {
746765
hsm.replace_default_auth(&passwd_new)
747766
}
748767
HsmCommand::Generate { key_spec } => hsm.generate(&key_spec),
749-
HsmCommand::Restore => {
750-
hsm.restore_wrap()?;
751-
oks::hsm::restore(&hsm.client, &hsm.state_dir)?;
768+
HsmCommand::Restore { backups, verifier } => {
769+
let verifier = fs::read_to_string(verifier)?;
770+
let verifier: Verifier = serde_json::from_str(&verifier)?;
771+
let share_itr = StdioShareReader::new(verifier);
772+
773+
let mut shares: Zeroizing<Vec<Share>> =
774+
Zeroizing::new(Vec::new());
775+
for share in share_itr {
776+
shares.deref_mut().push(*share?.deref());
777+
if shares.len() >= THRESHOLD {
778+
break;
779+
}
780+
}
781+
782+
hsm.restore_wrap(shares)?;
783+
oks::hsm::restore(&hsm.client, backups)?;
752784
info!("Deleting default authentication key");
753785
oks::hsm::delete(&hsm.client, 1, Type::AuthenticationKey)
754786
}

0 commit comments

Comments
 (0)