diff --git a/go.mod b/go.mod index 8a7a185a8a..3f9bfdff77 100644 --- a/go.mod +++ b/go.mod @@ -143,6 +143,7 @@ require ( github.com/gosimple/unidecode v1.0.1 // indirect github.com/jjti/go-spancheck v0.6.1 // indirect github.com/lasiar/canonicalheader v1.1.1 // indirect + github.com/minio/highwayhash v1.0.3 // indirect github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417 // indirect github.com/quasilyte/go-ruleguard/dsl v0.3.22 // indirect github.com/samber/slog-common v0.17.0 // indirect @@ -381,7 +382,7 @@ require ( golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f // indirect golang.org/x/net v0.26.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect - golang.org/x/sys v0.21.0 // indirect + golang.org/x/sys v0.22.0 // indirect golang.org/x/term v0.21.0 // indirect golang.org/x/text v0.16.0 // indirect golang.org/x/tools v0.22.0 // indirect diff --git a/go.sum b/go.sum index 68a4db81d5..af33ad3950 100644 --- a/go.sum +++ b/go.sum @@ -1336,6 +1336,8 @@ github.com/mgechev/revive v1.3.7 h1:502QY0vQGe9KtYJ9FpxMz9rL+Fc/P13CI5POL4uHCcE= github.com/mgechev/revive v1.3.7/go.mod h1:RJ16jUbF0OWC3co/+XTxmFNgEpUPwnnA0BRllX2aDNA= github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= +github.com/minio/highwayhash v1.0.3 h1:kbnuUMoHYyVl7szWjSxJnxw11k2U709jqFPPmIUyD6Q= +github.com/minio/highwayhash v1.0.3/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= @@ -1997,6 +1999,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/telemetry v0.0.0-20240522233618-39ace7a40ae7 h1:FemxDzfMUcK2f3YY4H+05K9CDzbSVr2+q/JKN45pey0= golang.org/x/telemetry v0.0.0-20240522233618-39ace7a40ae7/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= diff --git a/internal/datastore/proxy/relationshipintegrity.go b/internal/datastore/proxy/relationshipintegrity.go index 985728da8f..cc61416cbc 100644 --- a/internal/datastore/proxy/relationshipintegrity.go +++ b/internal/datastore/proxy/relationshipintegrity.go @@ -3,10 +3,8 @@ package proxy import ( "context" "crypto/hmac" - "crypto/sha256" "fmt" "hash" - "sync" "time" "google.golang.org/protobuf/types/known/timestamppb" @@ -16,6 +14,7 @@ import ( corev1 "github.com/authzed/spicedb/pkg/proto/core/v1" "github.com/authzed/spicedb/pkg/spiceerrors" "github.com/authzed/spicedb/pkg/tuple" + "github.com/minio/highwayhash" ) // KeyConfig is a configuration for a key used to sign relationships. @@ -28,11 +27,10 @@ type KeyConfig struct { type hmacConfig struct { keyID string expiredAt *time.Time - hmacPool *sync.Pool + hasher hash.Hash } var ( - alg = sha256.New versionByte = byte(0x01) ) @@ -40,12 +38,15 @@ var ( // relationships by using HMACs to sign the data. The current key is used to sign new data, // and the expired keys are used to verify old data, if any. func NewRelationshipIntegrityProxy(ds datastore.Datastore, currentKey KeyConfig, expiredKeys []KeyConfig) (datastore.Datastore, error) { + hasher, err := hasherForKey(currentKey.Bytes) + if err != nil { + return nil, err + } + currentKeyHMAC := hmacConfig{ keyID: currentKey.ID, expiredAt: currentKey.ExpiredAt, - hmacPool: &sync.Pool{ - New: func() any { return hmac.New(alg, currentKey.Bytes) }, - }, + hasher: hasher, } if currentKey.ExpiredAt != nil { @@ -64,12 +65,15 @@ func NewRelationshipIntegrityProxy(ds datastore.Datastore, currentKey KeyConfig, return nil, spiceerrors.MustBugf("found duplicate key ID: %s", key.ID) } + hasher, err := hasherForKey(key.Bytes) + if err != nil { + return nil, err + } + keysByID[key.ID] = hmacConfig{ keyID: key.ID, expiredAt: key.ExpiredAt, - hmacPool: &sync.Pool{ - New: func() any { return hmac.New(alg, key.Bytes) }, - }, + hasher: hasher, } } @@ -80,6 +84,10 @@ func NewRelationshipIntegrityProxy(ds datastore.Datastore, currentKey KeyConfig, }, nil } +func hasherForKey(key []byte) (hash.Hash, error) { + return highwayhash.New64(key) +} + type relationshipIntegrityProxy struct { ds datastore.Datastore primaryKey hmacConfig @@ -97,15 +105,14 @@ func (r *relationshipIntegrityProxy) lookupKey(keyID string) (hmacConfig, error) // computeRelationshipHash computes the HMAC hash of a relationship tuple. func computeRelationshipHash(tpl *corev1.RelationTuple, key hmacConfig) ([]byte, error) { - hmac := key.hmacPool.Get().(hash.Hash) - defer key.hmacPool.Put(hmac) - defer hmac.Reset() + hasher := key.hasher + hasher.Reset() bytes := tuple.MustCanonicalBytes(tpl) - if _, err := hmac.Write(bytes); err != nil { + if _, err := hasher.Write(bytes); err != nil { return nil, err } - return hmac.Sum(nil)[:16], nil + return hasher.Sum(nil)[:8], nil } func (r *relationshipIntegrityProxy) SnapshotReader(rev datastore.Revision) datastore.Reader { @@ -264,7 +271,7 @@ func (r *relationshipIntegrityIterator) Next() *corev1.RelationTuple { } hashWithoutByte := tpl.Integrity.Hash[1:] - if tpl.Integrity.Hash[0] != versionByte || len(hashWithoutByte) != 16 { + if tpl.Integrity.Hash[0] != versionByte || len(hashWithoutByte) != 8 { r.err = fmt.Errorf("relationship %s has invalid integrity data", tpl) return nil } diff --git a/internal/datastore/proxy/relationshipintegrity_test.go b/internal/datastore/proxy/relationshipintegrity_test.go index a139413438..9e2e9849c1 100644 --- a/internal/datastore/proxy/relationshipintegrity_test.go +++ b/internal/datastore/proxy/relationshipintegrity_test.go @@ -2,9 +2,8 @@ package proxy import ( "context" - "crypto/hmac" + "encoding/hex" "fmt" - "sync" "testing" "time" @@ -19,19 +18,37 @@ import ( ) var defaultKeyForTesting = KeyConfig{ - ID: "defaultfortest", - Bytes: []byte("somedefaultkeyfortesting"), + ID: "defaultfortest", + Bytes: (func() []byte { + b, err := hex.DecodeString("000102030405060708090A0B0C0D0E0FF0E0D0C0B0A090807060504030201000") + if err != nil { + panic(err) + } + return b + })(), ExpiredAt: nil, } var toBeExpiredKeyForTesting = KeyConfig{ - ID: "expiredkeyfortest", - Bytes: []byte("somexpiredkeyfortesting"), + ID: "expiredkeyfortest", + Bytes: (func() []byte { + b, err := hex.DecodeString("000102030405060708090A0B0C0D0E0FF0E0D0C0B0A090807060504030201222") + if err != nil { + panic(err) + } + return b + })(), } var expiredKeyForTesting = KeyConfig{ - ID: "expiredkeyfortest", - Bytes: []byte("somexpiredkeyfortesting"), + ID: "expiredkeyfortest", + Bytes: (func() []byte { + b, err := hex.DecodeString("000102030405060708090A0B0C0D0E0FF0E0D0C0B0A090807060504030201222") + if err != nil { + panic(err) + } + return b + })(), ExpiredAt: (func() *time.Time { t, err := time.Parse("2006-01-02", "2021-01-01") if err != nil { @@ -42,8 +59,14 @@ var expiredKeyForTesting = KeyConfig{ } var notYetExpiredKeyForTesting = KeyConfig{ - ID: "expiredkeyfortest", - Bytes: []byte("somexpiredkeyfortesting"), + ID: "expiredkeyfortest", + Bytes: (func() []byte { + b, err := hex.DecodeString("000102030405060708090A0B0C0D0E0FF0E0D0C0B0A090807060504030201222") + if err != nil { + panic(err) + } + return b + })(), ExpiredAt: (func() *time.Time { t, err := time.Parse("2006-01-02", "2999-01-01") if err != nil { @@ -296,7 +319,7 @@ func TestBasicIntegrityFailureDueToInvalidHashSignature(t *testing.T) { invalidTpl := tuple.MustParse("resource:foo#viewer@user:jimmy") invalidTpl.Integrity = &core.RelationshipIntegrity{ KeyId: "defaultfortest", - Hash: append([]byte{0x01}, []byte("someinvalidhashaasd")[0:16]...), + Hash: append([]byte{0x01}, []byte("someinvalidhashaasd")[0:8]...), HashedAt: timestamppb.Now(), } @@ -519,11 +542,12 @@ func BenchmarkQueryRelsWithIntegrity(b *testing.B) { } func BenchmarkComputeRelationshipHash(b *testing.B) { + hasher, err := hasherForKey(defaultKeyForTesting.Bytes) + require.NoError(b, err) + config := hmacConfig{ - keyID: "defaultfortest", - hmacPool: &sync.Pool{ - New: func() any { return hmac.New(alg, []byte("sometestbytes")) }, - }, + keyID: "defaultfortest", + hasher: hasher, } tpl := tuple.MustParse("resource:foo#viewer@user:tom")