diff --git a/src/uu/cksum/locales/en-US.ftl b/src/uu/cksum/locales/en-US.ftl index 338ae874c..0506d1bbe 100644 --- a/src/uu/cksum/locales/en-US.ftl +++ b/src/uu/cksum/locales/en-US.ftl @@ -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) diff --git a/src/uu/cksum/locales/fr-FR.ftl b/src/uu/cksum/locales/fr-FR.ftl index 52b4b9ed7..1a045dddb 100644 --- a/src/uu/cksum/locales/fr-FR.ftl +++ b/src/uu/cksum/locales/fr-FR.ftl @@ -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) diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index 4de22cf8a..37b9165fe 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -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::(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()); } }; diff --git a/src/uu/hashsum/src/hashsum.rs b/src/uu/hashsum/src/hashsum.rs index f97ff2ba2..ca85f870f 100644 --- a/src/uu/hashsum/src/hashsum.rs +++ b/src/uu/hashsum/src/hashsum.rs @@ -101,7 +101,7 @@ fn create_algorithm_from_flags(matches: &ArgMatches) -> UResult { if matches.get_flag("sha3") { match matches.get_one::("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 { 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 { create_fn: Box::new(|| Box::new(Shake256::new())), bits: *bits, })?, - None => return Err(ChecksumError::BitsRequiredForShake256.into()), + None => return Err(ChecksumError::LengthRequired("SHAKE256".into()).into()), } } diff --git a/src/uucore/src/lib/features/checksum.rs b/src/uucore/src/lib/features/checksum.rs index eae482bb2..30eccc254 100644 --- a/src/uucore/src/lib/features/checksum.rs +++ b/src/uucore/src/lib/features/checksum.rs @@ -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 { 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 { + 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) -> UResult 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) -> UResult 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> { } } +pub fn validate_sha2_sha3_length(algo_name: &str, length: Option) -> UResult { + 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, &'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] diff --git a/tests/by-util/test_cksum.rs b/tests/by-util/test_cksum.rs index 392efe688..4e7d6fd41 100644 --- a/tests/by-util/test_cksum.rs +++ b/tests/by-util/test_cksum.rs @@ -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]