Skip to content

Commit 366d5b5

Browse files
committed
Refactor the Alphabet & Hsm types into separate modules.
This is easy / possible now that the Hsm type implements RngCore.
1 parent e9222c5 commit 366d5b5

File tree

5 files changed

+95
-87
lines changed

5 files changed

+95
-87
lines changed

src/alphabet.rs

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4+
5+
use anyhow::{Context, Result};
6+
use rand_core::RngCore;
7+
use std::collections::HashSet;
8+
9+
pub struct Alphabet {
10+
chars: Vec<char>,
11+
}
12+
13+
impl Default for Alphabet {
14+
fn default() -> Self {
15+
Self::new()
16+
}
17+
}
18+
19+
impl Alphabet {
20+
pub fn new() -> Self {
21+
let mut chars: HashSet<char> = HashSet::new();
22+
chars.extend('a'..='z');
23+
chars.extend('A'..='Z');
24+
chars.extend('0'..='9');
25+
26+
// Remove visually similar characters
27+
chars = &chars - &HashSet::from(['l', 'I', '1']);
28+
chars = &chars - &HashSet::from(['B', '8']);
29+
chars = &chars - &HashSet::from(['O', '0']);
30+
31+
// We generate random passwords from this alphabet by getting a byte
32+
// of random data from the HSM and using this value to pick
33+
// characters from the alphabet. Our alphabet cannot be larger than
34+
// the u8::MAX or it will ignore characters after the u8::MAXth.
35+
assert!(usize::from(u8::MAX) > chars.len());
36+
37+
Alphabet {
38+
chars: chars.into_iter().collect(),
39+
}
40+
}
41+
42+
pub fn get_char(&self, val: u8) -> Option<char> {
43+
let len = self.chars.len() as u8;
44+
// let rand = ;
45+
// Avoid biasing results by ensuring the random values we use
46+
// are a multiple of the length of the alphabet. If they aren't
47+
// we just get another.
48+
if val < u8::MAX - u8::MAX % len {
49+
Some(self.chars[(val % len) as usize])
50+
} else {
51+
None
52+
}
53+
}
54+
55+
pub fn get_random_string<R: RngCore>(
56+
&self,
57+
rng: &mut R,
58+
length: usize,
59+
) -> Result<String> {
60+
let mut passwd = String::with_capacity(length + 1);
61+
let mut byte = [0u8, 1];
62+
63+
for i in 0..length {
64+
let char = loop {
65+
rng.try_fill_bytes(&mut byte).with_context(|| {
66+
format!("failed to get byte {} for password", i)
67+
})?;
68+
69+
if let Some(char) = self.get_char(byte[0]) {
70+
break char;
71+
}
72+
};
73+
74+
passwd.push(char);
75+
}
76+
77+
Ok(passwd)
78+
}
79+
}

src/bin/printer-test.rs

+7-4
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ use std::path::PathBuf;
77
use anyhow::Result;
88
use clap::{Parser, Subcommand};
99
use hex::ToHex;
10-
use oks::{backup::Share, hsm::Alphabet, secret_writer::PrinterSecretWriter};
11-
use rand::{thread_rng, Rng};
10+
use oks::{
11+
alphabet::Alphabet, backup::Share, secret_writer::PrinterSecretWriter,
12+
};
13+
use rand::thread_rng;
1214
use zeroize::Zeroizing;
1315

1416
#[derive(Parser)]
@@ -56,8 +58,9 @@ fn main() -> Result<()> {
5658
secret_writer.share(share_idx, share_count, &share)
5759
}
5860
Command::HsmPassword { length } => {
59-
let password = Alphabet::new()
60-
.get_random_string(|| Ok(thread_rng().gen::<u8>()), length)?;
61+
let mut rng = thread_rng();
62+
let password =
63+
Alphabet::new().get_random_string(&mut rng, length)?;
6164
let password = Zeroizing::new(password);
6265
secret_writer.password(&password)
6366
}

src/hsm.rs

-79
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ use anyhow::{Context, Result};
66
use log::{debug, error, info};
77
use pem_rfc7468::LineEnding;
88
use rand_core::{impls, CryptoRng, Error as RngError, RngCore};
9-
use std::collections::HashSet;
109
use std::{
1110
fs,
1211
io::{self, Write},
@@ -64,81 +63,11 @@ pub enum HsmError {
6463
NotEnoughShares,
6564
}
6665

67-
pub struct Alphabet {
68-
chars: Vec<char>,
69-
}
70-
71-
impl Default for Alphabet {
72-
fn default() -> Self {
73-
Self::new()
74-
}
75-
}
76-
77-
impl Alphabet {
78-
pub fn new() -> Self {
79-
let mut chars: HashSet<char> = HashSet::new();
80-
chars.extend('a'..='z');
81-
chars.extend('A'..='Z');
82-
chars.extend('0'..='9');
83-
84-
// Remove visually similar characters
85-
chars = &chars - &HashSet::from(['l', 'I', '1']);
86-
chars = &chars - &HashSet::from(['B', '8']);
87-
chars = &chars - &HashSet::from(['O', '0']);
88-
89-
// We generate random passwords from this alphabet by getting a byte
90-
// of random data from the HSM and using this value to pick
91-
// characters from the alphabet. Our alphabet cannot be larger than
92-
// the u8::MAX or it will ignore characters after the u8::MAXth.
93-
assert!(usize::from(u8::MAX) > chars.len());
94-
95-
Alphabet {
96-
chars: chars.into_iter().collect(),
97-
}
98-
}
99-
100-
pub fn get_char(&self, val: u8) -> Option<char> {
101-
let len = self.chars.len() as u8;
102-
// let rand = ;
103-
// Avoid biasing results by ensuring the random values we use
104-
// are a multiple of the length of the alphabet. If they aren't
105-
// we just get another.
106-
if val < u8::MAX - u8::MAX % len {
107-
Some(self.chars[(val % len) as usize])
108-
} else {
109-
None
110-
}
111-
}
112-
113-
pub fn get_random_string(
114-
&self,
115-
get_rand_u8: impl Fn() -> Result<u8>,
116-
length: usize,
117-
) -> Result<String> {
118-
let mut passwd = String::with_capacity(length + 1);
119-
120-
for _ in 0..length {
121-
let char = loop {
122-
let rand = get_rand_u8()?;
123-
124-
if let Some(char) = self.get_char(rand) {
125-
break char;
126-
}
127-
};
128-
129-
passwd.push(char);
130-
}
131-
132-
Ok(passwd)
133-
}
134-
}
135-
13666
/// Structure holding common data used by OKS when interacting with the HSM.
13767
pub struct Hsm {
13868
pub client: Client,
13969
pub out_dir: PathBuf,
14070
pub state_dir: PathBuf,
141-
pub alphabet: Alphabet,
14271
pub backup: bool,
14372
}
14473

@@ -179,18 +108,10 @@ impl Hsm {
179108
client,
180109
out_dir: out_dir.to_path_buf(),
181110
state_dir: state_dir.to_path_buf(),
182-
alphabet: Alphabet::new(),
183111
backup,
184112
})
185113
}
186114

187-
pub fn rand_string(&self, length: usize) -> Result<String> {
188-
self.alphabet.get_random_string(
189-
|| Ok(self.client.get_pseudo_random(1)?[0]),
190-
length,
191-
)
192-
}
193-
194115
/// Create a new wrap key, cut it up into shares, & a Feldman verifier,
195116
/// then put the key into the YubiHSM. The shares and the verifier are then
196117
/// returned to the caller. Generally they will then be distributed

src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// License, v. 2.0. If a copy of the MPL was not distributed with this
33
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
44

5+
pub mod alphabet;
56
pub mod backup;
67
pub mod ca;
78
pub mod config;

src/main.rs

+8-4
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use yubihsm::object::{Id, Type};
1717
use zeroize::Zeroizing;
1818

1919
use oks::{
20+
alphabet::Alphabet,
2021
backup::{BackupKey, Share, Verifier, LIMIT, THRESHOLD},
2122
ca::{Ca, CertOrCsr},
2223
config::{
@@ -259,7 +260,7 @@ fn get_passwd(
259260
}
260261

261262
/// get a new password from the environment or by issuing a challenge the user
262-
fn get_new_passwd(hsm: Option<&Hsm>) -> Result<Zeroizing<String>> {
263+
fn get_new_passwd(hsm: Option<&mut Hsm>) -> Result<Zeroizing<String>> {
263264
let passwd = match env::var(ENV_NEW_PASSWORD).ok() {
264265
// prefer new password from env above all else
265266
Some(s) => {
@@ -270,7 +271,10 @@ fn get_new_passwd(hsm: Option<&Hsm>) -> Result<Zeroizing<String>> {
270271
// use the HSM otherwise if available
271272
Some(hsm) => {
272273
info!("Generating random password");
273-
Zeroizing::new(hsm.rand_string(GEN_PASSWD_LENGTH)?)
274+
let alpha = Alphabet::default();
275+
let password =
276+
alpha.get_random_string(&mut *hsm, GEN_PASSWD_LENGTH)?;
277+
Zeroizing::new(password)
274278
}
275279
// last option: challenge the caller
276280
None => {
@@ -364,7 +368,7 @@ fn do_ceremony<P: AsRef<Path>>(
364368
let passwd = if challenge {
365369
get_new_passwd(None)?
366370
} else {
367-
get_new_passwd(Some(&hsm))?
371+
get_new_passwd(Some(&mut hsm))?
368372
};
369373

370374
secret_writer.password(&passwd)?;
@@ -759,7 +763,7 @@ fn main() -> Result<()> {
759763
let passwd_new = if passwd_challenge {
760764
get_new_passwd(None)?
761765
} else {
762-
get_new_passwd(Some(&hsm))?
766+
get_new_passwd(Some(&mut hsm))?
763767
};
764768

765769
let secret_writer =

0 commit comments

Comments
 (0)