crypto/hmac: wrap ErrUnsupported returned by Clone

Updates #69521

Change-Id: I6a6a4656403b9d35d5e4641b5c5c4975f3fa0e43
Reviewed-on: https://go-review.googlesource.com/c/go/+/675555
Reviewed-by: Austin Clements <austin@google.com>
Auto-Submit: Filippo Valsorda <filippo@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Roland Shoemaker <roland@golang.org>
This commit is contained in:
Filippo Valsorda 2025-05-22 18:00:02 +02:00 committed by Gopher Robot
parent 03ad694dcb
commit 4731832342
3 changed files with 32 additions and 8 deletions

View file

@ -11,6 +11,7 @@ import (
"crypto/sha1" "crypto/sha1"
"crypto/sha256" "crypto/sha256"
"crypto/sha512" "crypto/sha512"
"errors"
"fmt" "fmt"
"hash" "hash"
"testing" "testing"
@ -583,6 +584,18 @@ func TestHMAC(t *testing.T) {
} }
} }
func TestNoClone(t *testing.T) {
h := New(func() hash.Hash { return justHash{sha256.New()} }, []byte("key"))
if _, ok := h.(hash.Cloner); !ok {
t.Skip("no Cloner support")
}
h.Write([]byte("test"))
_, err := h.(hash.Cloner).Clone()
if !errors.Is(err, errors.ErrUnsupported) {
t.Errorf("Clone() = %v, want ErrUnsupported", err)
}
}
func TestNonUniqueHash(t *testing.T) { func TestNonUniqueHash(t *testing.T) {
if boring.Enabled { if boring.Enabled {
t.Skip("hash.Hash provided by boringcrypto are not comparable") t.Skip("hash.Hash provided by boringcrypto are not comparable")

View file

@ -130,26 +130,36 @@ func (h *HMAC) Reset() {
h.marshaled = true h.marshaled = true
} }
type errCloneUnsupported struct{}
func (e errCloneUnsupported) Error() string {
return "crypto/hmac: hash does not support hash.Cloner"
}
func (e errCloneUnsupported) Unwrap() error {
return errors.ErrUnsupported
}
// Clone implements [hash.Cloner] if the underlying hash does. // Clone implements [hash.Cloner] if the underlying hash does.
// Otherwise, it returns [errors.ErrUnsupported]. // Otherwise, it returns an error wrapping [errors.ErrUnsupported].
func (h *HMAC) Clone() (hash.Cloner, error) { func (h *HMAC) Clone() (hash.Cloner, error) {
r := *h r := *h
ic, ok := h.inner.(hash.Cloner) ic, ok := h.inner.(hash.Cloner)
if !ok { if !ok {
return nil, errors.ErrUnsupported return nil, errCloneUnsupported{}
} }
oc, ok := h.outer.(hash.Cloner) oc, ok := h.outer.(hash.Cloner)
if !ok { if !ok {
return nil, errors.ErrUnsupported return nil, errCloneUnsupported{}
} }
var err error var err error
r.inner, err = ic.Clone() r.inner, err = ic.Clone()
if err != nil { if err != nil {
return nil, errors.ErrUnsupported return nil, errCloneUnsupported{}
} }
r.outer, err = oc.Clone() r.outer, err = oc.Clone()
if err != nil { if err != nil {
return nil, errors.ErrUnsupported return nil, errCloneUnsupported{}
} }
return &r, nil return &r, nil
} }

View file

@ -57,13 +57,14 @@ type Hash64 interface {
Sum64() uint64 Sum64() uint64
} }
// A Cloner is a hash function whose state can be cloned. // A Cloner is a hash function whose state can be cloned, returning a value with
// equivalent and independent state.
// //
// All [Hash] implementations in the standard library implement this interface, // All [Hash] implementations in the standard library implement this interface,
// unless GOFIPS140=v1.0.0 is set. // unless GOFIPS140=v1.0.0 is set.
// //
// If a hash can only determine at runtime if it can be cloned, // If a hash can only determine at runtime if it can be cloned (e.g. if it wraps
// (e.g., if it wraps another hash), it may return [errors.ErrUnsupported]. // another hash), it may return an error wrapping [errors.ErrUnsupported].
type Cloner interface { type Cloner interface {
Hash Hash
Clone() (Cloner, error) Clone() (Cloner, error)