Fix HKDF and HMAC with SHA-3 under system crypto backends#2360
Conversation
Backport the crypto/sha3, crypto/internal/fips140hash and crypto/hmac hunks of d06bfbe (PR #2149) to release-branch.go1.26. Fixes #2356. When a system crypto backend is active and supports SHA-3, sha3.New256 (and friends) return a SHA3 whose backend hash is stored in boringS while the embedded Go digest s is left zero. fips140hash_sha3Unwrap returned &s.s, the empty Go digest, whose Size() is 0, so HKDF computed limit := 0 * 255 and rejected any positive key length with the misleading error "hkdf: requested key length too large". Return boringS when the SHA3 is backend-backed, and widen the sha3Unwrap linkname to hash.Hash. Also reorder hmac.New so boring.NewHMAC runs after the hash is unwrapped, so the backend receives its own hash type instead of the wrapped *SHA3.
There was a problem hiding this comment.
Pull request overview
Fixes a system-crypto–backend-specific SHA-3 wrapping bug that broke consumers relying on correct hash.Hash metadata (notably HKDF and HMAC), by ensuring the unwrap path returns a functional backend hash and by unwrapping before backend HMAC selection.
Changes:
- Update the SHA-3 unwrap linkname path to return the backend-backed hash when present (and widen the linkname signature to
hash.Hash). - Reorder
crypto/hmac.Newso the hash constructor is unwrapped before attemptingboring.NewHMAC, preventing backend type mismatches. - (Also in this patch) expand SHA-3/SHAKE/CSHAKE routing through the system-crypto backend.
Comments suppressed due to low confidence (1)
patches/0004-Use-crypto-backends.patch:2995
- The PR description’s “Fix” section describes changes limited to sha3 unwrap + HMAC ordering, but this patch also introduces broader SHA-3/SHAKE/CSHAKE routing through the system-crypto backend (e.g., Sum256/SumSHAKE256 and constructors now selecting backend implementations). If this broader behavior change is intended, please reflect it in the PR description (or consider splitting it out) so reviewers understand the full scope and risk surface.
--- a/src/crypto/sha3/sha3.go
+++ b/src/crypto/sha3/sha3.go
@@ -8,6 +8,7 @@ package sha3
import (
"crypto"
+ boring "crypto/internal/backend"
"crypto/internal/fips140/sha3"
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Cover the two failure modes fixed in the previous commit:
* TestHKDFSHA3 derives a 32-byte key with SHA3-256 and fails if HKDF
reports "requested key length too large", the symptom of the
backend-backed SHA-3 hash reporting Size() == 0. Backends that do not
implement HKDF with SHA-3 (for example macOS CryptoKit) skip instead.
* TestHMACSHA3 builds an HMAC with SHA3-256 and checks it returns,
guarding against the infinite loop. The macOS CryptoKit backend
advertises SHA-3 hashing but cannot compute HMAC-SHA3 and aborts the
process, so the test skips there; that is a separate backend
limitation.
Address review feedback: skip TestHKDFSHA3 only when the macOS CryptoKit backend genuinely lacks HKDF-SHA3, and fail on any other error instead of turning unexpected failures into skips. This keeps the test guarding behavior on Linux/OpenSSL and Windows/CNG.
The CI darwin builders run on a macOS where CryptoKit does not expose SHA-3 hashing, so boring.SupportsHash(crypto.SHA3_256) is false there and TestHKDFSHA3/TestHMACSHA3 fell through to a failure instead of skipping. Skip on the CryptoKit backend purely by build (boring.Enabled and GOOS=="darwin"), which is independent of the macOS version, since CryptoKit never supports HKDF or HMAC with SHA-3. OpenSSL, CNG, and the pure Go path still run the assertions. Drop the now-unused crypto import.
| +// constructor received the still-wrapped *sha3.SHA3 instead of the unwrapped | ||
| +// hash. The hash must now be unwrapped before the backend sees it. | ||
| +func TestHMACSHA3(t *testing.T) { | ||
| + if boring.Enabled && runtime.GOOS == "darwin" { |
There was a problem hiding this comment.
hmac.New returns nil if the backend doesn't support the given hash. You can skip when that happens instead of hardcoding darwin here.
| + // not issue #2356. OpenSSL, CNG, and the pure Go path all support it, so | ||
| + // only skip on the CryptoKit backend and fail otherwise rather than | ||
| + // hiding an unexpected error. | ||
| + if boring.Enabled && runtime.GOOS == "darwin" { |
There was a problem hiding this comment.
If this is a real issue, then create an issue to track it, thanks!
There was a problem hiding this comment.
Problem
In 1.26, SHA-3 is routed through the system crypto backend (e.g. OpenSSL on
Linux). When a backend is active and supports SHA-3,
sha3.New256(and theother constructors) return a
SHA3whose backend hash is stored inboringS,while the embedded pure-Go digest
sis left as a zero value.fips140hash_sha3Unwrapreturned&s.s— that empty Go digest — whoseSize()andBlockSize()are both0. Unwrapping is used by HKDF and HMAC,so:
limit := Size() * 255 == 0and rejected any positive keylength with the misleading error
hkdf: requested key length too large.BlockSize()of0, writing the key into a zero-ratesponge whose absorb loop never makes progress — an infinite hang.
Both symptoms are specific to system-crypto builds; the pure-Go backend
(
GOEXPERIMENT=nosystemcrypto) and upstream Go are unaffected.Fix
boringSfromfips140hash_sha3Unwrapwhen theSHA3isbackend-backed, and widen the
sha3Unwraplinkname signature tohash.Hash.hmac.Newsoboring.NewHMACruns after the hash is unwrapped,so the backend receives its own hash type instead of the wrapped
*SHA3.Testing
hkdf.Key(sha3.New256, …)now succeeds.openssl mac -digest SHA3-256.Related: