util(cksum): Add support for sha2 and sha3

This commit is contained in:
Dorian Peron 2025-10-26 02:48:20 +02:00
parent 8f6e7201fa
commit dfda5e0d91
6 changed files with 164 additions and 92 deletions

View file

@ -8,10 +8,8 @@ cksum-after-help = DIGEST determines the digest algorithm and default output for
- crc32b: (only available through cksum)
- md5: (equivalent to md5sum)
- sha1: (equivalent to sha1sum)
- sha224: (equivalent to sha224sum)
- sha256: (equivalent to sha256sum)
- sha384: (equivalent to sha384sum)
- sha512: (equivalent to sha512sum)
- sha2: (equivalent to sha{"{224,256,384,512}"}sum)
- sha3: (only available through cksum)
- blake2b: (equivalent to b2sum)
- sm3: (only available through cksum)

View file

@ -8,10 +8,8 @@ cksum-after-help = DIGEST détermine l'algorithme de condensé et le format de s
- crc32b : (disponible uniquement via cksum)
- md5 : (équivalent à md5sum)
- sha1 : (équivalent à sha1sum)
- sha224 : (équivalent à sha224sum)
- sha256 : (équivalent à sha256sum)
- sha384 : (équivalent à sha384sum)
- sha512 : (équivalent à sha512sum)
- sha2: (équivalent à sha{"{224,256,384,512}"}sum)
- sha3 : (disponible uniquement via cksum)
- blake2b : (équivalent à b2sum)
- sm3 : (disponible uniquement via cksum)

View file

@ -14,9 +14,10 @@ use std::iter;
use std::path::Path;
use uucore::checksum::{
ALGORITHM_OPTIONS_BLAKE2B, ALGORITHM_OPTIONS_BSD, ALGORITHM_OPTIONS_CRC,
ALGORITHM_OPTIONS_CRC32B, ALGORITHM_OPTIONS_SYSV, ChecksumError, ChecksumOptions,
ChecksumVerbose, HashAlgorithm, LEGACY_ALGORITHMS, SUPPORTED_ALGORITHMS,
calculate_blake2b_length, detect_algo, digest_reader, perform_checksum_validation,
ALGORITHM_OPTIONS_CRC32B, ALGORITHM_OPTIONS_SHA2, ALGORITHM_OPTIONS_SHA3,
ALGORITHM_OPTIONS_SYSV, ChecksumError, ChecksumOptions, ChecksumVerbose, HashAlgorithm,
LEGACY_ALGORITHMS, SUPPORTED_ALGORITHMS, calculate_blake2b_length, detect_algo, digest_reader,
perform_checksum_validation,
};
use uucore::translate;
@ -382,13 +383,16 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let input_length = matches.get_one::<usize>(options::LENGTH);
let length = match input_length {
None | Some(0) => None,
Some(length) if algo_name == ALGORITHM_OPTIONS_BLAKE2B => {
calculate_blake2b_length(*length)?
}
let length = match (input_length, algo_name) {
// Length for sha2 and sha3 should be saved, it will be validated
// afterwards if necessary.
(Some(len), ALGORITHM_OPTIONS_SHA2 | ALGORITHM_OPTIONS_SHA3) => Some(*len),
(None | Some(0), _) => None,
// Length for Blake2b if saved only if it's not zero.
(Some(len), ALGORITHM_OPTIONS_BLAKE2B) => calculate_blake2b_length(*len)?,
// a --length flag set with any other algorithm is an error.
_ => {
return Err(ChecksumError::LengthOnlyForBlake2b.into());
return Err(ChecksumError::LengthOnlyForBlake2bSha2Sha3.into());
}
};

View file

@ -101,7 +101,7 @@ fn create_algorithm_from_flags(matches: &ArgMatches) -> UResult<HashAlgorithm> {
if matches.get_flag("sha3") {
match matches.get_one::<usize>("bits") {
Some(bits) => set_or_err(create_sha3(*bits)?)?,
None => return Err(ChecksumError::BitsRequiredForSha3.into()),
None => return Err(ChecksumError::LengthRequired("SHA3".into()).into()),
}
}
if matches.get_flag("sha3-224") {
@ -139,7 +139,7 @@ fn create_algorithm_from_flags(matches: &ArgMatches) -> UResult<HashAlgorithm> {
create_fn: Box::new(|| Box::new(Shake128::new())),
bits: *bits,
})?,
None => return Err(ChecksumError::BitsRequiredForShake128.into()),
None => return Err(ChecksumError::LengthRequired("SHAKE128".into()).into()),
}
}
if matches.get_flag("shake256") {
@ -149,7 +149,7 @@ fn create_algorithm_from_flags(matches: &ArgMatches) -> UResult<HashAlgorithm> {
create_fn: Box::new(|| Box::new(Shake256::new())),
bits: *bits,
})?,
None => return Err(ChecksumError::BitsRequiredForShake256.into()),
None => return Err(ChecksumError::LengthRequired("SHAKE256".into()).into()),
}
}

View file

@ -35,6 +35,7 @@ pub const ALGORITHM_OPTIONS_CRC: &str = "crc";
pub const ALGORITHM_OPTIONS_CRC32B: &str = "crc32b";
pub const ALGORITHM_OPTIONS_MD5: &str = "md5";
pub const ALGORITHM_OPTIONS_SHA1: &str = "sha1";
pub const ALGORITHM_OPTIONS_SHA2: &str = "sha2";
pub const ALGORITHM_OPTIONS_SHA3: &str = "sha3";
pub const ALGORITHM_OPTIONS_SHA224: &str = "sha224";
@ -47,21 +48,23 @@ pub const ALGORITHM_OPTIONS_SM3: &str = "sm3";
pub const ALGORITHM_OPTIONS_SHAKE128: &str = "shake128";
pub const ALGORITHM_OPTIONS_SHAKE256: &str = "shake256";
pub const SUPPORTED_ALGORITHMS: [&str; 16] = [
pub const SUPPORTED_ALGORITHMS: [&str; 17] = [
ALGORITHM_OPTIONS_SYSV,
ALGORITHM_OPTIONS_BSD,
ALGORITHM_OPTIONS_CRC,
ALGORITHM_OPTIONS_CRC32B,
ALGORITHM_OPTIONS_MD5,
ALGORITHM_OPTIONS_SHA1,
ALGORITHM_OPTIONS_SHA2,
ALGORITHM_OPTIONS_SHA3,
ALGORITHM_OPTIONS_BLAKE2B,
ALGORITHM_OPTIONS_SM3,
// Extra algorithms that are not valid `cksum --algorithm`
ALGORITHM_OPTIONS_SHA224,
ALGORITHM_OPTIONS_SHA256,
ALGORITHM_OPTIONS_SHA384,
ALGORITHM_OPTIONS_SHA512,
ALGORITHM_OPTIONS_BLAKE2B,
ALGORITHM_OPTIONS_BLAKE3,
ALGORITHM_OPTIONS_SM3,
ALGORITHM_OPTIONS_SHAKE128,
ALGORITHM_OPTIONS_SHAKE256,
];
@ -215,20 +218,16 @@ pub enum ChecksumError {
StrictNotCheck,
#[error("the --quiet option is meaningful only when verifying checksums")]
QuietNotCheck,
#[error("Invalid output size for SHA3 (expected 224, 256, 384, or 512)")]
InvalidOutputSizeForSha3,
#[error("--bits required for SHA3")]
BitsRequiredForSha3,
#[error("--bits required for SHAKE128")]
BitsRequiredForShake128,
#[error("--bits required for SHAKE256")]
BitsRequiredForShake256,
#[error("unknown algorithm: clap should have prevented this case")]
UnknownAlgorithm,
#[error("--length required for {}", .0.quote())]
LengthRequired(String),
#[error("unknown algorithm: {0}: clap should have prevented this case")]
UnknownAlgorithm(String),
#[error("length is not a multiple of 8")]
InvalidLength,
#[error("--length is only supported with --algorithm=blake2b")]
LengthOnlyForBlake2b,
#[error("digest length for {} must be 224, 256, 384, or 512", .0.quote())]
InvalidLengthFor(String),
#[error("--length is only supported with --algorithm blake2b, sha2, or sha3")]
LengthOnlyForBlake2bSha2Sha3,
#[error("the --binary and --text options are meaningless when verifying checksums")]
BinaryTextConflict,
#[error("--text mode is only supported with --untagged")]
@ -258,27 +257,54 @@ impl UError for ChecksumError {
pub fn create_sha3(bits: usize) -> UResult<HashAlgorithm> {
match bits {
224 => Ok(HashAlgorithm {
name: "SHA3_224",
name: "SHA3-224",
create_fn: Box::new(|| Box::new(Sha3_224::new())),
bits: 224,
}),
256 => Ok(HashAlgorithm {
name: "SHA3_256",
name: "SHA3-256",
create_fn: Box::new(|| Box::new(Sha3_256::new())),
bits: 256,
}),
384 => Ok(HashAlgorithm {
name: "SHA3_384",
name: "SHA3-384",
create_fn: Box::new(|| Box::new(Sha3_384::new())),
bits: 384,
}),
512 => Ok(HashAlgorithm {
name: "SHA3_512",
name: "SHA3-512",
create_fn: Box::new(|| Box::new(Sha3_512::new())),
bits: 512,
}),
_ => Err(ChecksumError::InvalidOutputSizeForSha3.into()),
_ => Err(ChecksumError::InvalidLengthFor("SHA3".into()).into()),
}
}
pub fn create_sha2(bits: usize) -> UResult<HashAlgorithm> {
match bits {
224 => Ok(HashAlgorithm {
name: "SHA224",
create_fn: Box::new(|| Box::new(Sha224::new())),
bits: 224,
}),
256 => Ok(HashAlgorithm {
name: "SHA256",
create_fn: Box::new(|| Box::new(Sha256::new())),
bits: 256,
}),
384 => Ok(HashAlgorithm {
name: "SHA384",
create_fn: Box::new(|| Box::new(Sha384::new())),
bits: 384,
}),
512 => Ok(HashAlgorithm {
name: "SHA512",
create_fn: Box::new(|| Box::new(Sha512::new())),
bits: 512,
}),
_ => Err(ChecksumError::InvalidLengthFor("SHA2".into()).into()),
}
}
@ -398,26 +424,10 @@ pub fn detect_algo(algo: &str, length: Option<usize>) -> UResult<HashAlgorithm>
create_fn: Box::new(|| Box::new(Sha1::new())),
bits: 160,
}),
ALGORITHM_OPTIONS_SHA224 | "sha224sum" => Ok(HashAlgorithm {
name: ALGORITHM_OPTIONS_SHA224,
create_fn: Box::new(|| Box::new(Sha224::new())),
bits: 224,
}),
ALGORITHM_OPTIONS_SHA256 | "sha256sum" => Ok(HashAlgorithm {
name: ALGORITHM_OPTIONS_SHA256,
create_fn: Box::new(|| Box::new(Sha256::new())),
bits: 256,
}),
ALGORITHM_OPTIONS_SHA384 | "sha384sum" => Ok(HashAlgorithm {
name: ALGORITHM_OPTIONS_SHA384,
create_fn: Box::new(|| Box::new(Sha384::new())),
bits: 384,
}),
ALGORITHM_OPTIONS_SHA512 | "sha512sum" => Ok(HashAlgorithm {
name: ALGORITHM_OPTIONS_SHA512,
create_fn: Box::new(|| Box::new(Sha512::new())),
bits: 512,
}),
ALGORITHM_OPTIONS_SHA224 | "sha224sum" => Ok(create_sha2(224)?),
ALGORITHM_OPTIONS_SHA256 | "sha256sum" => Ok(create_sha2(256)?),
ALGORITHM_OPTIONS_SHA384 | "sha384sum" => Ok(create_sha2(384)?),
ALGORITHM_OPTIONS_SHA512 | "sha512sum" => Ok(create_sha2(512)?),
ALGORITHM_OPTIONS_BLAKE2B | "b2sum" => {
// Set default length to 512 if None
let bits = length.unwrap_or(512);
@ -445,28 +455,38 @@ pub fn detect_algo(algo: &str, length: Option<usize>) -> UResult<HashAlgorithm>
create_fn: Box::new(|| Box::new(Sm3::new())),
bits: 512,
}),
ALGORITHM_OPTIONS_SHAKE128 | "shake128sum" => {
let bits = length.ok_or(ChecksumError::BitsRequiredForShake128)?;
algo @ (ALGORITHM_OPTIONS_SHAKE128 | "shake128sum") => {
let bits = length.ok_or(ChecksumError::LengthRequired(algo.to_ascii_uppercase()))?;
Ok(HashAlgorithm {
name: ALGORITHM_OPTIONS_SHAKE128,
create_fn: Box::new(|| Box::new(Shake128::new())),
bits,
})
}
ALGORITHM_OPTIONS_SHAKE256 | "shake256sum" => {
let bits = length.ok_or(ChecksumError::BitsRequiredForShake256)?;
algo @ (ALGORITHM_OPTIONS_SHAKE256 | "shake256sum") => {
let bits = length.ok_or(ChecksumError::LengthRequired(algo.to_ascii_uppercase()))?;
Ok(HashAlgorithm {
name: ALGORITHM_OPTIONS_SHAKE256,
create_fn: Box::new(|| Box::new(Shake256::new())),
bits,
})
}
_ if algo.starts_with("sha3") => {
let bits = length.ok_or(ChecksumError::BitsRequiredForSha3)?;
algo @ ALGORITHM_OPTIONS_SHA2 => {
let bits = validate_sha2_sha3_length(algo, length)?;
create_sha2(bits)
}
algo @ ALGORITHM_OPTIONS_SHA3 => {
let bits = validate_sha2_sha3_length(algo, length)?;
create_sha3(bits)
}
_ => Err(ChecksumError::UnknownAlgorithm.into()),
// TODO: `hashsum` specific, to remove once hashsum is removed.
algo @ ("sha3-224" | "sha3-256" | "sha3-384" | "sha3-512") => {
let bits: usize = algo.strip_prefix("sha3-").unwrap().parse().unwrap();
create_sha3(bits)
}
algo => Err(ChecksumError::UnknownAlgorithm(algo.into()).into()),
}
}
@ -833,7 +853,9 @@ fn identify_algo_name_and_length(
}
let bytes = if let Some(bitlen) = line_info.algo_bit_len {
if algorithm != ALGORITHM_OPTIONS_BLAKE2B || bitlen % 8 != 0 {
match algorithm.as_str() {
ALGORITHM_OPTIONS_BLAKE2B if bitlen % 8 == 0 => Some(bitlen / 8),
ALGORITHM_OPTIONS_SHA3 if [224, 256, 384, 512].contains(&bitlen) => Some(bitlen),
// Either
// the algo based line is provided with a bit length
// with an algorithm that does not support it (only Blake2B does).
@ -842,9 +864,8 @@ fn identify_algo_name_and_length(
// ^ This is illegal
// OR
// the given length is wrong because it's not a multiple of 8.
return Err(LineCheckError::ImproperlyFormatted);
_ => return Err(LineCheckError::ImproperlyFormatted),
}
Some(bitlen / 8)
} else if algorithm == ALGORITHM_OPTIONS_BLAKE2B {
// Default length with BLAKE2b,
Some(64)
@ -939,15 +960,24 @@ fn process_non_algo_based_line(
let expected_checksum = get_expected_digest_as_hex_string(line_info, None)
.ok_or(LineCheckError::ImproperlyFormatted)?;
// When a specific algorithm name is input, use it and use the provided bits
// except when dealing with blake2b, where we will detect the length
let (algo_name, algo_byte_len) = if cli_algo_name == ALGORITHM_OPTIONS_BLAKE2B {
// division by 2 converts the length of the Blake2b checksum from hexadecimal
// characters to bytes, as each byte is represented by two hexadecimal characters.
let length = Some(expected_checksum.len() / 2);
(ALGORITHM_OPTIONS_BLAKE2B.to_string(), length)
} else {
(cli_algo_name.to_lowercase(), cli_algo_length)
// When a specific algorithm name is input, use it and use the provided
// bits except when dealing with blake2b, sha2 and sha3, where we will
// detect the length.
let (algo_name, algo_byte_len) = match cli_algo_name {
ALGORITHM_OPTIONS_BLAKE2B => {
// division by 2 converts the length of the Blake2b checksum from
// hexadecimal characters to bytes, as each byte is represented by
// two hexadecimal characters.
(
ALGORITHM_OPTIONS_BLAKE2B.to_string(),
Some(expected_checksum.len() / 2),
)
}
algo @ (ALGORITHM_OPTIONS_SHA2 | ALGORITHM_OPTIONS_SHA3) => {
// multiplication by 4 to get the number of bits
(algo.to_string(), Some(expected_checksum.len() * 4))
}
_ => (cli_algo_name.to_lowercase(), cli_algo_length),
};
let algo = detect_algo(&algo_name, algo_byte_len)?;
@ -1216,6 +1246,17 @@ pub fn calculate_blake2b_length(length: usize) -> UResult<Option<usize>> {
}
}
pub fn validate_sha2_sha3_length(algo_name: &str, length: Option<usize>) -> UResult<usize> {
match length {
Some(len @ (224 | 256 | 384 | 512)) => Ok(len),
Some(len) => {
show_error!("invalid length: '{len}'");
Err(ChecksumError::InvalidLengthFor(algo_name.to_ascii_uppercase()).into())
}
None => Err(ChecksumError::LengthRequired(algo_name.to_ascii_uppercase()).into()),
}
}
pub fn unescape_filename(filename: &[u8]) -> (Vec<u8>, &'static str) {
let mut unescaped = Vec::with_capacity(filename.len());
let mut byte_iter = filename.iter().peekable();
@ -1327,19 +1368,19 @@ mod tests {
);
assert_eq!(
detect_algo(ALGORITHM_OPTIONS_SHA224, None).unwrap().name,
ALGORITHM_OPTIONS_SHA224
ALGORITHM_OPTIONS_SHA224.to_ascii_uppercase()
);
assert_eq!(
detect_algo(ALGORITHM_OPTIONS_SHA256, None).unwrap().name,
ALGORITHM_OPTIONS_SHA256
ALGORITHM_OPTIONS_SHA256.to_ascii_uppercase()
);
assert_eq!(
detect_algo(ALGORITHM_OPTIONS_SHA384, None).unwrap().name,
ALGORITHM_OPTIONS_SHA384
ALGORITHM_OPTIONS_SHA384.to_ascii_uppercase()
);
assert_eq!(
detect_algo(ALGORITHM_OPTIONS_SHA512, None).unwrap().name,
ALGORITHM_OPTIONS_SHA512
ALGORITHM_OPTIONS_SHA512.to_ascii_uppercase()
);
assert_eq!(
detect_algo(ALGORITHM_OPTIONS_BLAKE2B, None).unwrap().name,
@ -1365,12 +1406,35 @@ mod tests {
.name,
ALGORITHM_OPTIONS_SHAKE256
);
assert_eq!(detect_algo("sha3_224", Some(224)).unwrap().name, "SHA3_224");
assert_eq!(detect_algo("sha3_256", Some(256)).unwrap().name, "SHA3_256");
assert_eq!(detect_algo("sha3_384", Some(384)).unwrap().name, "SHA3_384");
assert_eq!(detect_algo("sha3_512", Some(512)).unwrap().name, "SHA3_512");
assert!(detect_algo("sha3_512", None).is_err());
// Older versions of checksum used to detect the "sha3" prefix, but not
// anymore.
assert!(detect_algo("sha3_224", Some(224)).is_err());
assert!(detect_algo("sha3_256", Some(256)).is_err());
assert!(detect_algo("sha3_384", Some(384)).is_err());
assert!(detect_algo("sha3_512", Some(512)).is_err());
let sha3_224 = detect_algo("sha3", Some(224)).unwrap();
assert_eq!(sha3_224.name, "SHA3-224");
assert_eq!(sha3_224.bits, 224);
let sha3_256 = detect_algo("sha3", Some(256)).unwrap();
assert_eq!(sha3_256.name, "SHA3-256");
assert_eq!(sha3_256.bits, 256);
let sha3_384 = detect_algo("sha3", Some(384)).unwrap();
assert_eq!(sha3_384.name, "SHA3-384");
assert_eq!(sha3_384.bits, 384);
let sha3_512 = detect_algo("sha3", Some(512)).unwrap();
assert_eq!(sha3_512.name, "SHA3-512");
assert_eq!(sha3_512.bits, 512);
assert!(detect_algo("sha3", None).is_err());
assert_eq!(detect_algo("sha2", Some(224)).unwrap().name, "SHA224");
assert_eq!(detect_algo("sha2", Some(256)).unwrap().name, "SHA256");
assert_eq!(detect_algo("sha2", Some(384)).unwrap().name, "SHA384");
assert_eq!(detect_algo("sha2", Some(512)).unwrap().name, "SHA512");
assert!(detect_algo("sha2", None).is_err());
}
#[test]

View file

@ -336,7 +336,9 @@ fn test_length_with_wrong_algorithm() {
.arg("lorem_ipsum.txt")
.fails_with_code(1)
.no_stdout()
.stderr_contains("cksum: --length is only supported with --algorithm=blake2b");
.stderr_contains(
"cksum: --length is only supported with --algorithm blake2b, sha2, or sha3",
);
new_ucmd!()
.arg("--length=16")
@ -345,7 +347,9 @@ fn test_length_with_wrong_algorithm() {
.arg("foo.sums")
.fails_with_code(1)
.no_stdout()
.stderr_contains("cksum: --length is only supported with --algorithm=blake2b");
.stderr_contains(
"cksum: --length is only supported with --algorithm blake2b, sha2, or sha3",
);
}
/// Giving --length to a wrong algorithm doesn't fail if the length is zero
@ -369,7 +373,9 @@ fn test_length_not_supported() {
.arg("lorem_ipsum.txt")
.fails_with_code(1)
.no_stdout()
.stderr_contains("--length is only supported with --algorithm=blake2b");
.stderr_contains(
"cksum: --length is only supported with --algorithm blake2b, sha2, or sha3",
);
new_ucmd!()
.arg("-l")
@ -380,7 +386,9 @@ fn test_length_not_supported() {
.arg("/tmp/xxx")
.fails_with_code(1)
.no_stdout()
.stderr_contains("--length is only supported with --algorithm=blake2b");
.stderr_contains(
"cksum: --length is only supported with --algorithm blake2b, sha2, or sha3",
);
}
#[test]