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