From 45c525cc004942676091c8836e8d6726144ba616 Mon Sep 17 00:00:00 2001 From: Deepak Jain Date: Mon, 11 May 2026 18:02:33 -0400 Subject: [PATCH] checksum: add SHA-3 (FIPS 202) via OpenSSL EVP Adds sha3-256, sha3-384 and sha3-512 to the set of algorithms that --checksum-choice accepts. Implementation is wholly through OpenSSL's EVP interface, so it only activates when rsync is built with --enable-openssl and the linked libcrypto exposes the SHA-3 family (OpenSSL >= 1.1.1). The existing verify_digest() probe automatically demotes any entry the local libcrypto does not understand to CSUM_gone, so listing the algorithms in valid_checksums_items[] is safe on older builds. SHA-3 is never selected by the "auto" negotiation path -- it has to be requested explicitly via --checksum-choice=sha3-* (single, comma-paired with itself, or as an entry in RSYNC_CHECKSUM_LIST). All defaults are unchanged; an unmodified peer keeps negotiating xxh128/xxh3/md5/MD4 as before. Because every checksum entry point (get_checksum2, file_checksum, sum_init/update/end) already short-circuits to the EVP path when xfer_sum_evp_md / file_sum_evp_md / cur_sum_evp_md is set, no extra case arms are needed for the rolling-block, whole-file, or streaming code paths -- SHA-3 just flows through them. The new lib/md-defines.h constants define the SHA-3 digest lengths (32 / 48 / 64 bytes) since OpenSSL never published SHA3_*_DIGEST_LENGTH macros; SHA3-512's 64-byte digest matches SHA-512, so MAX_DIGEST_LEN is unaffected. A testsuite case (testsuite/checksum-sha3.test) exercises whole-file, pre-transfer (-c), comma-pair and delta-update paths for all three SHA-3 variants, and self-skips when the local rsync was not built with OpenSSL or its libcrypto lacks SHA-3. Verified: - rsync --version now lists sha3-512 sha3-384 sha3-256 in "Checksum list". - rsync -a --checksum-choice=sha3-{256,384,512} round-trips byte- for-byte (verified with diff against source). - Computed SHA3-256("abc") matches the FIPS 202 vector 3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532. - With --debug=NSTR, auto negotiation still picks xxh128 by default; SHA-3 is only selected when explicitly requested. - "make check" passes the new test along with the existing suite (the unrelated 'devices' failure is pre-existing on this host and reproduces on unmodified upstream master). Signed-off-by: Deepak Jain --- NEWS.md | 11 +++++++ checksum.c | 20 ++++++++++++ lib/md-defines.h | 12 ++++++++ rsync.1.md | 7 +++++ testsuite/checksum-sha3.test | 60 ++++++++++++++++++++++++++++++++++++ 5 files changed, 110 insertions(+) create mode 100755 testsuite/checksum-sha3.test diff --git a/NEWS.md b/NEWS.md index c4a73d63f..3c659bafb 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,6 +2,17 @@ ## Changes in this version: +### ENHANCEMENTS: + +- Added SHA-3 (FIPS 202) as an opt-in `--checksum-choice` algorithm + family: `sha3-256`, `sha3-384`, and `sha3-512`. The implementation + uses OpenSSL EVP and so requires that rsync be built with OpenSSL + support and linked against libcrypto >= 1.1.1. Like `sha256` and + `sha512`, SHA-3 is never picked by `auto` negotiation; it must be + explicitly requested on both ends (e.g. + `--checksum-choice=sha3-256`) or made part of the + `RSYNC_CHECKSUM_LIST` environment override. + ### BUG FIXES: - Fixed a regression introduced by the 3.4.0 secure_relative_open() diff --git a/checksum.c b/checksum.c index 24e46bfbb..f357aa5a9 100644 --- a/checksum.c +++ b/checksum.c @@ -54,6 +54,15 @@ struct name_num_item valid_checksums_items[] = { #ifdef SUPPORT_XXHASH { CSUM_XXH64, 0, "xxh64", NULL }, { CSUM_XXH64, 0, "xxhash", NULL }, +#endif +#ifdef USE_OPENSSL + /* SHA-3 is offered only via OpenSSL EVP (sha3-* digests, OpenSSL >= 1.1.1). + * verify_digest() probes each entry at startup and disables any that the + * linked libcrypto does not support, so listing them here is safe even + * when the build environment is older. */ + { CSUM_SHA3_512, NNI_EVP, "sha3-512", NULL }, + { CSUM_SHA3_384, NNI_EVP, "sha3-384", NULL }, + { CSUM_SHA3_256, NNI_EVP, "sha3-256", NULL }, #endif { CSUM_MD5, NNI_BUILTIN|NNI_EVP, "md5", NULL }, { CSUM_MD4, NNI_BUILTIN|NNI_EVP, "md4", NULL }, @@ -237,6 +246,14 @@ int csum_len_for_type(int cst, BOOL flist_csum) #ifdef SHA512_DIGEST_LENGTH case CSUM_SHA512: return SHA512_DIGEST_LENGTH; +#endif +#ifdef USE_OPENSSL + case CSUM_SHA3_256: + return SHA3_256_DIGEST_LEN; + case CSUM_SHA3_384: + return SHA3_384_DIGEST_LEN; + case CSUM_SHA3_512: + return SHA3_512_DIGEST_LEN; #endif case CSUM_XXH64: case CSUM_XXH3_64: @@ -266,6 +283,9 @@ int canonical_checksum(int csum_type) case CSUM_SHA1: case CSUM_SHA256: case CSUM_SHA512: + case CSUM_SHA3_256: + case CSUM_SHA3_384: + case CSUM_SHA3_512: return -1; case CSUM_XXH64: case CSUM_XXH3_64: diff --git a/lib/md-defines.h b/lib/md-defines.h index 6ef6a6897..0b58c575c 100644 --- a/lib/md-defines.h +++ b/lib/md-defines.h @@ -10,6 +10,15 @@ #define MD4_DIGEST_LEN 16 #define MD5_DIGEST_LEN 16 + +/* SHA-3 (FIPS 202). OpenSSL exposes these via EVP only, with no + * SHA3_*_DIGEST_LENGTH macros, so define our own. SHA3-512 shares + * its 64-byte digest length with SHA-512, so MAX_DIGEST_LEN is + * unaffected. */ +#define SHA3_256_DIGEST_LEN 32 +#define SHA3_384_DIGEST_LEN 48 +#define SHA3_512_DIGEST_LEN 64 + #if defined SHA512_DIGEST_LENGTH #define MAX_DIGEST_LEN SHA512_DIGEST_LENGTH #elif defined SHA256_DIGEST_LENGTH @@ -35,3 +44,6 @@ #define CSUM_SHA1 9 #define CSUM_SHA256 10 #define CSUM_SHA512 11 +#define CSUM_SHA3_256 12 +#define CSUM_SHA3_384 13 +#define CSUM_SHA3_512 14 diff --git a/rsync.1.md b/rsync.1.md index 2b4b75087..0e51d44c3 100644 --- a/rsync.1.md +++ b/rsync.1.md @@ -1781,11 +1781,18 @@ expand it. - `xxh128` - `xxh3` - `xxh64` (aka `xxhash`) + - `sha3-512` + - `sha3-384` + - `sha3-256` - `md5` - `md4` - `sha1` - `none` + The `sha3-*` choices require an rsync built with OpenSSL support and + a libcrypto that provides SHA-3 (OpenSSL 1.1.1 or newer). They are + never selected by `auto` negotiation and must be requested explicitly. + Run `rsync --version` to see the default checksum list compiled into your version (which may differ from the list above). diff --git a/testsuite/checksum-sha3.test b/testsuite/checksum-sha3.test new file mode 100755 index 000000000..37a4a129c --- /dev/null +++ b/testsuite/checksum-sha3.test @@ -0,0 +1,60 @@ +#!/bin/sh + +# Test SHA-3 checksum support (sha3-256, sha3-384, sha3-512). +# +# Requires rsync to be built with OpenSSL support and linked against a +# libcrypto that exposes the SHA-3 family (OpenSSL 1.1.1 or newer). +# The test probes for SHA-3 by asking rsync to use it on a no-op copy; +# if the local rsync rejects the choice (e.g. no OpenSSL at build time, +# or older libcrypto), the test is skipped rather than failed. + +. "$suitedir/rsync.fns" + +probe() { + $RSYNC --checksum-choice="$1" --help >/dev/null 2>&1 || return 1 + # --help doesn't actually parse the choice; force a real parse. + mkdir -p "$tmpdir/probe.src" + : > "$tmpdir/probe.src/x" + rm -rf "$tmpdir/probe.dst" + $RSYNC -a --checksum-choice="$1" "$tmpdir/probe.src/" "$tmpdir/probe.dst/" 2>"$outfile" + rc=$? + rm -rf "$tmpdir/probe.src" "$tmpdir/probe.dst" + if [ $rc -ne 0 ] && grep -q 'unknown checksum name' "$outfile"; then + return 1 + fi + return $rc +} + +if ! probe sha3-256; then + test_skipped "rsync was not built with OpenSSL SHA-3 support" +fi + +mkdir "$fromdir" +mkdir "$fromdir/sub" +# Mix of empty, small, and chunk-spanning sizes (CHUNK_SIZE is 32k internally). +: > "$fromdir/empty" +echo "hello sha-3" > "$fromdir/small" +dd if=/dev/urandom of="$fromdir/big" bs=1024 count=70 2>/dev/null + +for algo in sha3-256 sha3-384 sha3-512; do + rm -rf "$todir" + + # Whole-file path (transfer checksum only). + checkit "$RSYNC -a --checksum-choice=$algo '$fromdir/' '$todir/'" "$fromdir" "$todir" + + # Pre-transfer + transfer checksum path (-c forces file_checksum to run). + rm -rf "$todir" + checkit "$RSYNC -ac --checksum-choice=$algo '$fromdir/' '$todir/'" "$fromdir" "$todir" + + # --checksum-choice with distinct xfer,file pair. + rm -rf "$todir" + checkit "$RSYNC -ac --checksum-choice=$algo,$algo '$fromdir/' '$todir/'" "$fromdir" "$todir" +done + +# Delta-update path: mutate the destination, re-sync with SHA-3, confirm match. +rm -rf "$todir" +$RSYNC -a --checksum-choice=sha3-256 "$fromdir/" "$todir/" || test_fail "initial sync failed" +echo "modified locally" >> "$todir/small" +checkit "$RSYNC -a --checksum-choice=sha3-256 '$fromdir/' '$todir/'" "$fromdir" "$todir" + +exit 0