use std::fs;
use std::path::{Path, PathBuf};
use crate::crypto;
use crate::error::{Error, Result};
use crate::etree::{self, ParseOps};
use super::{CommonArgs, Operation, config, consts, validate};
pub(super) fn make_policy(name: &str) -> Box<dyn crypto::CryptoPolicy> {
match name {
"default" => Box::new(crypto::CryptoPolicyDefault {}),
"nist" => Box::new(crypto::CryptoPolicyNIST {}),
other => panic!(
"make_policy: unknown policy '{}' (callers must validate via VALID_POLICIES)",
other
),
}
}
pub(super) fn with_config<F>(common: CommonArgs, f: F) -> Result<()>
where
F: FnOnce(CommonArgs) -> Result<()>,
{
let common = apply_config(common)?;
validate_common(&common)?;
f(common)
}
fn validate_common(common: &CommonArgs) -> Result<()> {
let issues = validate::collect(common);
validate::report(&issues)
}
pub(super) fn apply_config(mut common: CommonArgs) -> Result<CommonArgs> {
let cfg = config::Config::load(&PathBuf::from("."))?;
if common.casdir.is_none() {
common.casdir = cfg.casdir.clone();
}
if common.policy.is_none() {
common.policy = cfg.policy.clone();
}
if common.defaults.is_none() {
common.defaults = cfg.defaults.clone();
}
if common.lang.is_none() {
common.lang = cfg.lang.clone();
}
let _ = crate::i18n::resolve_locale(cfg.locale.as_deref());
if common.signer.is_none() {
common.signer = cfg.chain.signer.as_ref().map(PathBuf::from);
}
if cfg.chain.auto_anchor == Some(true) {
common.anchor = true;
}
if cfg.fips == Some(true) {
common.fips = true;
}
Ok(common)
}
pub(super) fn build_anchor_config(
anchor_flag: bool,
signer_path: Option<&Path>,
op_kind: Option<Operation>,
words: &[String],
) -> Result<etree::AnchorConfig> {
if !anchor_flag {
return Ok(etree::AnchorConfig::disabled());
}
let signer_path = signer_path.ok_or_else(|| Error::InvalidArg {
arg: "anchor",
reason: "--anchor requires --signer <PRIV.pem>".to_string(),
})?;
let priv_pem = fs::read_to_string(signer_path)?;
let op = op_kind
.map(|k| k.label())
.unwrap_or("passthrough")
.to_string();
Ok(etree::AnchorConfig {
enabled: true,
operation: op,
words: words.to_vec(),
signer_priv_pem: Some(priv_pem),
})
}
pub(super) fn walk_for_chains(
tree: &etree::TextTree,
out: &mut Vec<crate::ledger::AnchorHash>,
) -> Result<()> {
etree::visitor::visit(tree, &mut |node| {
if let etree::TextNode::Chain { extfields } = node
&& let Ok(signed) = crate::ledger::SignedAnchor::from_extfields(extfields)
&& let Ok(h) = signed.id()
{
out.push(h);
}
etree::visitor::Control::Continue
});
Ok(())
}
pub(super) fn resolve_policy_name(common: &CommonArgs) -> Result<String> {
let explicit_policy = common.policy.clone();
let mut policy_name = explicit_policy
.clone()
.unwrap_or_else(|| consts::DEFAULT_POLICY.to_string());
let fips = common.fips
|| (cfg!(unix)
&& match fs::read_to_string("/proc/sys/crypto/fips_enabled") {
Ok(s) => s.starts_with('1'),
Err(_) => false,
});
if fips {
if let Some(p) = explicit_policy.as_deref()
&& p != "nist"
{
return Err(Error::InvalidArg {
arg: "--policy",
reason: format!("--fips forces --policy=nist but --policy={p} was set"),
});
}
policy_name = "nist".to_string();
}
Ok(policy_name)
}
pub(super) fn resolve_policy(common: &CommonArgs) -> Result<Box<dyn crypto::CryptoPolicy>> {
Ok(make_policy(&resolve_policy_name(common)?))
}
pub(super) fn resolve_separators(common: &CommonArgs) -> (String, String) {
if let Some(ref lang) = common.lang
&& let Some((left, right)) = consts::lang_separators(lang)
{
let l = if common.left_separator == consts::DEFAULT_LEFT_SEP {
left.to_string()
} else {
common.left_separator.clone()
};
let r = if common.right_separator == consts::DEFAULT_RIGHT_SEP {
right.to_string()
} else {
common.right_separator.clone()
};
return (l, r);
}
(
common.left_separator.clone(),
common.right_separator.clone(),
)
}
pub(super) fn apply_common(common: &CommonArgs, paops: &mut ParseOps) {
if let Some(dir) = common.casdir.clone() {
paops.io.set_local_casdir(dir);
} else if Path::new("cas").is_dir() {
paops.io.set_local_casdir(Path::new("cas").to_path_buf());
} else {
paops.io.set_local_casdir(Path::new(".").to_path_buf());
}
paops.io.verbose = common.verbose && !common.quiet;
paops.io.inline_data = common.inline || common.casdir.is_none();
paops.io.dry_run = common.dry_run;
paops.max_depth = common.max_depth;
let (left, right) = resolve_separators(common);
paops.separators.left = left;
paops.separators.right = right;
paops.passwords.extend(common.password.clone());
}
#[cfg(test)]
mod tests {
use super::*;
fn args() -> CommonArgs {
CommonArgs::for_filter(None)
}
#[test]
fn policy_resolution_prefers_explicit_over_fips_default() {
let mut c = args();
c.fips = true;
c.policy = Some("nist".to_string());
assert_eq!(resolve_policy_name(&c).unwrap(), "nist");
let mut c = args();
c.fips = true;
assert_eq!(resolve_policy_name(&c).unwrap(), "nist");
}
#[test]
fn policy_resolution_defaults_without_flags() {
let c = args();
assert_eq!(resolve_policy_name(&c).unwrap(), "default");
}
#[test]
fn separators_fall_back_to_defaults_without_lang() {
let c = args();
let (left, right) = resolve_separators(&c);
assert_eq!((left.as_str(), right.as_str()), ("// <(", ")>"));
}
#[test]
fn make_policy_knows_both_policies() {
let _ = make_policy("default");
let _ = make_policy("nist");
}
#[test]
#[should_panic(expected = "unknown policy")]
fn make_policy_panics_on_programming_error() {
let _ = make_policy("klingon");
}
}