use std::ffi::OsString; use std::fmt; use std::fs::File; #[cfg(feature = "hmac")] use std::io::Read; use std::io::{stdin, BufRead, BufReader, Error as IoError, Result as IoResult}; use std::marker::PhantomData; #[cfg(feature = "hmac")] use std::os::unix::ffi::OsStringExt; #[cfg(feature = "hmac")] use std::os::unix::io::{FromRawFd, RawFd}; use std::path::Path; use std::process; use clap::{ArgEnum, Parser}; use crypto_common::typenum::{IsLess, Le, NonZero, U256}; use crypto_common::AlgorithmName; #[cfg(feature = "hmac")] use crypto_common::{BlockSizeUser, KeyInit}; use digest::core_api::{BufferKindUser, CoreWrapper}; use digest::DynDigest; #[cfg(feature = "hmac")] use digest::{Digest, FixedOutputReset}; #[cfg(feature = "hmac")] use hmac::{Hmac, SimpleHmac}; #[cfg(feature = "groestl")] mod grøstl { pub(crate) use groestl::{ Groestl224 as Grøstl224, Groestl256 as Grøstl256, Groestl384 as Grøstl384, Groestl512 as Grøstl512, }; } #[derive(Parser)] #[clap(version, author, about)] struct Options { /// Hash algorithm to use #[clap(short, long, arg_enum)] algorithm: Algorithm, /// Reverse the format of the output #[clap(short, long)] reverse: bool, /// MAC key #[cfg(feature = "hmac")] #[clap(long, env, hide_env_values(true), parse(from_os_str))] mac_key: Option, /// File descriptor for MAC key #[cfg(feature = "hmac")] #[clap(long)] mac_key_fd: Option, /// Files to hash; stdin by default #[clap(parse(from_os_str))] files: Vec, } struct ProcessedOptions { algorithm: Algorithm, reverse: bool, #[cfg(feature = "hmac")] mac_key: Option>, files: Vec, } impl TryFrom for ProcessedOptions { type Error = IoError; fn try_from(options: Options) -> IoResult { #[cfg(feature = "hmac")] let mac_key = if let Some(fd) = options.mac_key_fd { if options.mac_key.is_some() { eprintln!("--mac-key-fd specified, ignoring $MAC_KEY"); } let mut result = Vec::new(); unsafe { File::from_raw_fd(fd) }.read_to_end(&mut result)?; Some(result) } else { options.mac_key.map(OsStringExt::into_vec) }; Ok(Self { algorithm: options.algorithm, reverse: options.reverse, #[cfg(feature = "hmac")] mac_key, files: options.files, }) } } impl ProcessedOptions { fn new_hasher(&self) -> (Box, String) { #[cfg(feature = "hmac")] if let Some(ref key) = self.mac_key { return self.algorithm.new_mac_instance(key); } self.algorithm.new_instance() } } #[derive(ArgEnum, Clone, Copy, Debug, Eq, PartialEq)] enum Algorithm { #[cfg(feature = "blake2")] Blake2b, #[cfg(feature = "blake2")] Blake2s, #[cfg(feature = "blake3")] Blake3, #[cfg(feature = "fsb")] Fsb160, #[cfg(feature = "fsb")] Fsb224, #[cfg(feature = "fsb")] Fsb256, #[cfg(feature = "fsb")] Fsb384, #[cfg(feature = "fsb")] Fsb512, #[cfg(feature = "gost94")] Gost94CryptoPro, #[cfg(feature = "gost94")] Gost94s2015, #[cfg(feature = "groestl")] #[clap(name = "grøstl224")] Grøstl224, #[cfg(feature = "groestl")] #[clap(name = "grøstl256")] Grøstl256, #[cfg(feature = "groestl")] #[clap(name = "grøstl384")] Grøstl384, #[cfg(feature = "groestl")] #[clap(name = "grøstl512")] Grøstl512, #[cfg(feature = "md2")] Md2, #[cfg(feature = "md4")] Md4, #[cfg(feature = "md5")] Md5, #[cfg(feature = "ripemd")] Ripemd160, #[cfg(feature = "ripemd")] Ripemd256, #[cfg(feature = "ripemd")] Ripemd320, #[cfg(feature = "sha1")] Sha1, #[cfg(feature = "sha2")] Sha224, #[cfg(feature = "sha2")] Sha256, #[cfg(feature = "sha2")] Sha384, #[cfg(feature = "sha2")] Sha512, #[cfg(feature = "sha2")] Sha512_224, #[cfg(feature = "sha2")] Sha512_256, #[cfg(feature = "sha3")] Sha3_224, #[cfg(feature = "sha3")] Sha3_256, #[cfg(feature = "sha3")] Sha3_384, #[cfg(feature = "sha3")] Sha3_512, #[cfg(feature = "shabal")] Shabal192, #[cfg(feature = "shabal")] Shabal224, #[cfg(feature = "shabal")] Shabal256, #[cfg(feature = "shabal")] Shabal384, #[cfg(feature = "shabal")] Shabal512, #[cfg(feature = "sm3")] Sm3, #[cfg(feature = "streebog")] Streebog256, #[cfg(feature = "streebog")] Streebog512, #[cfg(feature = "tiger")] Tiger, #[cfg(feature = "tiger")] Tiger2, #[cfg(feature = "whirlpool")] Whirlpool, } impl Algorithm { fn new_instance(&self) -> (Box, String) { match *self { #[cfg(feature = "blake2")] Self::Blake2b => build::(), #[cfg(feature = "blake2")] Self::Blake2s => build::(), #[cfg(feature = "blake3")] Self::Blake3 => build::(), #[cfg(feature = "fsb")] Self::Fsb160 => build::(), #[cfg(feature = "fsb")] Self::Fsb224 => build::(), #[cfg(feature = "fsb")] Self::Fsb256 => build::(), #[cfg(feature = "fsb")] Self::Fsb384 => build::(), #[cfg(feature = "fsb")] Self::Fsb512 => build::(), #[cfg(feature = "gost94")] Self::Gost94CryptoPro => build::(), #[cfg(feature = "gost94")] Self::Gost94s2015 => build::(), #[cfg(feature = "groestl")] Self::Grøstl224 => build::(), #[cfg(feature = "groestl")] Self::Grøstl256 => build::(), #[cfg(feature = "groestl")] Self::Grøstl384 => build::(), #[cfg(feature = "groestl")] Self::Grøstl512 => build::(), #[cfg(feature = "md2")] Self::Md2 => build::(), #[cfg(feature = "md4")] Self::Md4 => build::(), #[cfg(feature = "md5")] Self::Md5 => build::(), #[cfg(feature = "ripemd")] Self::Ripemd160 => build::(), #[cfg(feature = "ripemd")] Self::Ripemd256 => build::(), #[cfg(feature = "ripemd")] Self::Ripemd320 => build::(), #[cfg(feature = "sha1")] Self::Sha1 => build::(), #[cfg(feature = "sha2")] Self::Sha224 => build::(), #[cfg(feature = "sha2")] Self::Sha256 => build::(), #[cfg(feature = "sha2")] Self::Sha384 => build::(), #[cfg(feature = "sha2")] Self::Sha512 => build::(), #[cfg(feature = "sha2")] Self::Sha512_224 => build::(), #[cfg(feature = "sha2")] Self::Sha512_256 => build::(), #[cfg(feature = "sha3")] Self::Sha3_224 => build::(), #[cfg(feature = "sha3")] Self::Sha3_256 => build::(), #[cfg(feature = "sha3")] Self::Sha3_384 => build::(), #[cfg(feature = "sha3")] Self::Sha3_512 => build::(), #[cfg(feature = "shabal")] Self::Shabal192 => build::(), #[cfg(feature = "shabal")] Self::Shabal224 => build::(), #[cfg(feature = "shabal")] Self::Shabal256 => build::(), #[cfg(feature = "shabal")] Self::Shabal384 => build::(), #[cfg(feature = "shabal")] Self::Shabal512 => build::(), #[cfg(feature = "sm3")] Self::Sm3 => build::(), #[cfg(feature = "streebog")] Self::Streebog256 => build::(), #[cfg(feature = "streebog")] Self::Streebog512 => build::(), #[cfg(feature = "tiger")] Self::Tiger => build::(), #[cfg(feature = "tiger")] Self::Tiger2 => build::(), #[cfg(feature = "whirlpool")] Self::Whirlpool => build::(), } } #[cfg(feature = "hmac")] fn new_mac_instance(&self, key: &[u8]) -> (Box, String) { match *self { #[cfg(feature = "blake2")] Self::Blake2b => build_mac::>(key), #[cfg(feature = "blake2")] Self::Blake2s => build_mac::>(key), #[cfg(feature = "blake3")] Self::Blake3 => build_mac::>(key), #[cfg(feature = "fsb")] Self::Fsb160 => build_mac::>(key), #[cfg(feature = "fsb")] Self::Fsb224 => build_mac::>(key), #[cfg(feature = "fsb")] Self::Fsb256 => build_mac::>(key), #[cfg(feature = "fsb")] Self::Fsb384 => build_mac::>(key), #[cfg(feature = "fsb")] Self::Fsb512 => build_mac::>(key), #[cfg(feature = "gost94")] Self::Gost94CryptoPro => build_mac::>(key), #[cfg(feature = "gost94")] Self::Gost94s2015 => build_mac::>(key), #[cfg(feature = "groestl")] Self::Grøstl224 => build_mac::>(key), #[cfg(feature = "groestl")] Self::Grøstl256 => build_mac::>(key), #[cfg(feature = "groestl")] Self::Grøstl384 => build_mac::>(key), #[cfg(feature = "groestl")] Self::Grøstl512 => build_mac::>(key), #[cfg(feature = "md2")] Self::Md2 => build_mac::>(key), #[cfg(feature = "md4")] Self::Md4 => build_mac::>(key), #[cfg(feature = "md5")] Self::Md5 => build_mac::>(key), #[cfg(feature = "ripemd")] Self::Ripemd160 => build_mac::>(key), #[cfg(feature = "ripemd")] Self::Ripemd256 => build_mac::>(key), #[cfg(feature = "ripemd")] Self::Ripemd320 => build_mac::>(key), #[cfg(feature = "sha1")] Self::Sha1 => build_mac::>(key), #[cfg(feature = "sha2")] Self::Sha224 => build_mac::>(key), #[cfg(feature = "sha2")] Self::Sha256 => build_mac::>(key), #[cfg(feature = "sha2")] Self::Sha384 => build_mac::>(key), #[cfg(feature = "sha2")] Self::Sha512 => build_mac::>(key), #[cfg(feature = "sha2")] Self::Sha512_224 => build_mac::>(key), #[cfg(feature = "sha2")] Self::Sha512_256 => build_mac::>(key), #[cfg(feature = "sha3")] Self::Sha3_224 => build_mac::>(key), #[cfg(feature = "sha3")] Self::Sha3_256 => build_mac::>(key), #[cfg(feature = "sha3")] Self::Sha3_384 => build_mac::>(key), #[cfg(feature = "sha3")] Self::Sha3_512 => build_mac::>(key), #[cfg(feature = "shabal")] Self::Shabal192 => build_mac::>(key), #[cfg(feature = "shabal")] Self::Shabal224 => build_mac::>(key), #[cfg(feature = "shabal")] Self::Shabal256 => build_mac::>(key), #[cfg(feature = "shabal")] Self::Shabal384 => build_mac::>(key), #[cfg(feature = "shabal")] Self::Shabal512 => build_mac::>(key), #[cfg(feature = "sm3")] Self::Sm3 => build_mac::>(key), #[cfg(feature = "streebog")] Self::Streebog256 => build_mac::>(key), #[cfg(feature = "streebog")] Self::Streebog512 => build_mac::>(key), #[cfg(feature = "tiger")] Self::Tiger => build_mac::>(key), #[cfg(feature = "tiger")] Self::Tiger2 => build_mac::>(key), #[cfg(feature = "whirlpool")] Self::Whirlpool => build_mac::>(key), } } } trait AbstractAlgorithmName { fn algorithm_name() -> String; } impl AbstractAlgorithmName for CoreWrapper where A::BlockSize: IsLess, Le: NonZero, { fn algorithm_name() -> String { struct Helper(PhantomData); impl fmt::Display for Helper { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { A::write_alg_name(f) } } Helper(PhantomData::).to_string() } } #[cfg(feature = "hmac")] impl AbstractAlgorithmName for SimpleHmac { fn algorithm_name() -> String { format!("Hmac<{}>", A::algorithm_name()) } } impl AbstractAlgorithmName for blake3::Hasher { fn algorithm_name() -> String { "Blake3".to_owned() } } trait AbstractDigest: AbstractAlgorithmName + Default + DynDigest {} impl AbstractDigest for D {} #[cfg(feature = "hmac")] trait AbstractMac: AbstractAlgorithmName + FixedOutputReset + KeyInit + Clone {} #[cfg(feature = "hmac")] impl AbstractMac for M {} fn build() -> (Box, String) { (Box::new(D::default()), D::algorithm_name()) } #[cfg(feature = "hmac")] fn build_mac(key: &[u8]) -> (Box, String) { (Box::new(M::new_from_slice(key).unwrap()), M::algorithm_name()) } fn main() -> IoResult<()> { let options = ProcessedOptions::try_from(Options::parse())?; let (mut hasher, name) = options.new_hasher(); if options.files.is_empty() { digest(hasher.as_mut(), stdin().lock())?; println!("{}", hex::encode(hasher.finalize())); } else { let mut failed = false; for ref arg in options.files { let lossy = arg.to_string_lossy(); match digest_file(hasher.as_mut(), arg) { Ok(..) => print_hash(&name, &*lossy, hasher.finalize_reset().into_vec(), options.reverse), Err(err) => { failed = true; eprintln!("{}: {}", lossy, err); hasher.reset(); } } } if failed { process::exit(1); } } Ok(()) } fn digest_file>(hasher: &mut D, path: P) -> IoResult<()> { digest(hasher, BufReader::new(File::open(path)?)) } fn digest(hasher: &mut D, mut reader: R) -> IoResult<()> { loop { let buf = reader.fill_buf()?; match buf.len() { 0 => return Ok(()), len => { hasher.update(buf); reader.consume(len); } } } } fn print_hash(algorithm_name: &str, file_name: &str, hash: Vec, reverse: bool) { let hex = hex::encode(hash); if reverse { println!("{hex} {file_name}"); } else { println!("{algorithm_name} ({file_name}) = {hex}"); } }