|
1 | 1 | //! Parses the operator's master xpriv from a file.
|
2 | 2 |
|
3 |
| -use std::{ |
4 |
| - env, |
5 |
| - fs::read_to_string, |
6 |
| - path::{Path, PathBuf}, |
7 |
| -}; |
| 3 | +use std::fs::read_to_string; |
8 | 4 |
|
9 | 5 | use bitcoin::bip32::Xpriv;
|
10 | 6 | use strata_key_derivation::operator::OperatorKeys;
|
11 |
| -use strata_primitives::keys::ZeroizableXpriv; |
| 7 | +use tracing::*; |
12 | 8 | use zeroize::Zeroize;
|
13 | 9 |
|
14 | 10 | /// The environment variable that contains the operator's master [`Xpriv`].
|
15 |
| -const OPXPRIV_ENVVAR: &str = "STRATA_OP_MASTER_XPRIV"; |
| 11 | +pub const OPXPRIV_ENVVAR: &str = "STRATA_OP_MASTER_XPRIV"; |
16 | 12 |
|
17 |
| -/// Parses the master [`Xpriv`] from a file. |
18 |
| -pub(crate) fn parse_master_xpriv(path: &Path) -> anyhow::Result<OperatorKeys> { |
19 |
| - let mut xpriv_str = read_to_string(path)?; |
20 |
| - match xpriv_str.parse::<Xpriv>() { |
21 |
| - Ok(mut xpriv) => { |
22 |
| - // Zeroize the xpriv string after parsing it. |
23 |
| - xpriv_str.zeroize(); |
24 |
| - |
25 |
| - // Parse into ZeroizableXpriv |
26 |
| - let zeroizable_xpriv: ZeroizableXpriv = xpriv.into(); |
27 |
| - |
28 |
| - // Zeroize the xpriv after parsing it. |
29 |
| - xpriv.private_key.non_secure_erase(); |
30 |
| - |
31 |
| - // Finally return the operator keys |
32 |
| - // |
33 |
| - // NOTE: `zeroizable_xpriv` is zeroized on drop. |
34 |
| - Ok(OperatorKeys::new(&zeroizable_xpriv) |
35 |
| - .map_err(|_| anyhow::anyhow!("invalid master xpriv"))?) |
36 |
| - } |
37 |
| - Err(e) => anyhow::bail!("invalid master xpriv: {}", e), |
38 |
| - } |
39 |
| -} |
40 |
| - |
41 |
| -/// Resolves the master [`Xpriv`] from CLI arguments or environment variables. |
| 13 | +/// Resolves the master [`Xpriv`] from the various sources. |
42 | 14 | ///
|
43 |
| -/// Precedence order for resolving the master xpriv: |
| 15 | +/// Rules: |
44 | 16 | ///
|
45 |
| -/// 1. If a key is supplied via the `--master-xpriv` CLI argument, it is used. |
46 |
| -/// 2. Otherwise, if a file path is supplied via CLI, the key is read from that file. |
47 |
| -/// 3. Otherwise, if the `STRATA_OP_MASTER_XPRIV` environment variable is set, its value is used. |
48 |
| -/// 4. Otherwise, returns an error. |
| 17 | +/// 1. If none are set, error out. |
| 18 | +/// 2. If multiple are set, error out. |
| 19 | +/// 3. If we have the verbatim key provided, parse it. |
| 20 | +/// 4. If we have a path provided, load it and parse that instead. |
49 | 21 | ///
|
50 | 22 | /// # Errors
|
51 | 23 | ///
|
52 |
| -/// Returns an error if the master xpriv is invalid or not found. |
| 24 | +/// Returns an error if the master xpriv is invalid or not found, or if |
| 25 | +/// conflicting options are set. |
53 | 26 | pub(crate) fn resolve_xpriv(
|
54 | 27 | cli_arg: Option<String>,
|
55 | 28 | cli_path: Option<String>,
|
| 29 | + env_val: Option<String>, |
56 | 30 | ) -> anyhow::Result<OperatorKeys> {
|
57 |
| - match (cli_arg, cli_path) { |
58 |
| - (Some(xpriv), _) => OperatorKeys::new(&xpriv.parse::<Xpriv>()?) |
59 |
| - .map_err(|_| anyhow::anyhow!("invalid master xpriv from CLI")), |
| 31 | + if cli_arg.is_some() { |
| 32 | + error!("FOUND CLI ARG KEY, THIS IS INSECURE!"); |
| 33 | + } |
60 | 34 |
|
61 |
| - (_, Some(path)) => parse_master_xpriv(&PathBuf::from(path)), |
| 35 | + let mut xpriv_str: String = match (cli_arg, cli_path, env_val) { |
| 36 | + // If there's none set then we error out. |
| 37 | + (None, None, None) => { |
| 38 | + anyhow::bail!( |
| 39 | + "must provide root xpriv with either `--master-xpriv-path` or {OPXPRIV_ENVVAR}" |
| 40 | + ) |
| 41 | + } |
62 | 42 |
|
63 |
| - (None, None) => match env::var(OPXPRIV_ENVVAR) { |
64 |
| - Ok(xpriv_env_str) => OperatorKeys::new(&xpriv_env_str.parse::<Xpriv>()?) |
65 |
| - .map_err(|_| anyhow::anyhow!("invalid master xpriv from envvar")), |
66 |
| - Err(_) => { |
67 |
| - anyhow::bail!( |
68 |
| - "must either set {OPXPRIV_ENVVAR} envvar or pass with `--master-xpriv`" |
69 |
| - ) |
70 |
| - } |
71 |
| - }, |
72 |
| - } |
| 43 | + // If multiple are set then we error out. |
| 44 | + (_, Some(_), Some(_)) | (Some(_), Some(_), _) | (Some(_), _, Some(_)) => { |
| 45 | + anyhow::bail!("multiple root xpriv options specified, don't know what to do, aborting"); |
| 46 | + } |
| 47 | + |
| 48 | + // In these cases we have the string explicitly. |
| 49 | + (Some(xpriv_str), _, _) | (_, _, Some(xpriv_str)) => xpriv_str.to_owned(), |
| 50 | + |
| 51 | + // In this case we fetch it from file. |
| 52 | + (_, Some(path), _) => read_to_string(path)?, |
| 53 | + }; |
| 54 | + |
| 55 | + // Some fancy dance to securely erase things. |
| 56 | + let Ok(raw) = xpriv_str.parse::<Xpriv>() else { |
| 57 | + xpriv_str.zeroize(); |
| 58 | + anyhow::bail!("invalid master xpriv"); |
| 59 | + }; |
| 60 | + |
| 61 | + let Ok(keys) = OperatorKeys::new(&raw) else { |
| 62 | + xpriv_str.zeroize(); |
| 63 | + // TODO how to secure erase raw? |
| 64 | + anyhow::bail!("unable to generate leaf keys"); |
| 65 | + }; |
| 66 | + |
| 67 | + Ok(keys) |
73 | 68 | }
|
0 commit comments