From 1dce6f3f41766069a2ca969214b8e69235e4a3ce Mon Sep 17 00:00:00 2001 From: Alexey Potapenko Date: Fri, 31 Oct 2025 08:20:16 +0300 Subject: [PATCH] integrity: signer and verifier implementation Closes #TNTP-4171 --- .golangci.yml | 2 + crypto/interfaces.go | 25 ++++++++++ crypto/rsa.go | 70 +++++++++++++++++++++++++++ crypto/rsa_test.go | 91 +++++++++++++++++++++++++++++++++++ go.mod | 26 +++++----- go.sum | 44 ++++++++--------- hasher/hasher.go | 4 ++ marshaller/marshaller.go | 59 +++++++++++++++++++++++ marshaller/marshaller_test.go | 59 +++++++++++++++++++++++ 9 files changed, 345 insertions(+), 35 deletions(-) create mode 100644 crypto/interfaces.go create mode 100644 crypto/rsa.go create mode 100644 crypto/rsa_test.go create mode 100644 marshaller/marshaller.go create mode 100644 marshaller/marshaller_test.go diff --git a/.golangci.yml b/.golangci.yml index 48d011b..85ee0e3 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -65,6 +65,7 @@ linters: - "github.com/tarantool/go-option" - "github.com/vmihailenco/msgpack/v5" - "github.com/tarantool/go-iproto" + - "gopkg.in/yaml.v3" test: files: - "$test" @@ -76,3 +77,4 @@ linters: - "github.com/tarantool/go-iproto" - "github.com/vmihailenco/msgpack/v5" - "github.com/gojuno/minimock/v3" + - "gopkg.in/yaml.v3" diff --git a/crypto/interfaces.go b/crypto/interfaces.go new file mode 100644 index 0000000..0dcbab3 --- /dev/null +++ b/crypto/interfaces.go @@ -0,0 +1,25 @@ +// Package crypto implements crypto interfaces. +package crypto + +// Signer implements high-level API for package signing. +type Signer interface { + // Name returns name of the crypto algorithm, used by signer. + Name() string + // Sign returns signature for passed data. + Sign(data []byte) ([]byte, error) +} + +// Verifier is an interface implementing a generic signature +// verification algorithm. +type Verifier interface { + // Name returns name of the crypto algorithm, used by verifier. + Name() string + // Verify checks data and signature mapping. + Verify(data []byte, signature []byte) error +} + +// SignerVerifier common interface. +type SignerVerifier interface { + Signer + Verifier +} diff --git a/crypto/rsa.go b/crypto/rsa.go new file mode 100644 index 0000000..0f2234e --- /dev/null +++ b/crypto/rsa.go @@ -0,0 +1,70 @@ +package crypto + +import ( + "crypto" + "crypto/rand" + "crypto/rsa" + "fmt" + + "github.com/tarantool/go-storage/hasher" +) + +// RSAPSS represents RSA PSS algo for signing/verification +// (with SHA256 as digest calculation function). +type RSAPSS struct { + publicKey rsa.PublicKey + privateKey rsa.PrivateKey + hash crypto.Hash + hasher hasher.Hasher +} + +// NewRSAPSS creates new RSAPSS object. +func NewRSAPSS(privKey rsa.PrivateKey, pubKey rsa.PublicKey) RSAPSS { + return RSAPSS{ + publicKey: pubKey, + privateKey: privKey, + hash: crypto.SHA256, + hasher: hasher.NewSHA256Hasher(), + } +} + +// Name implements SignerVerifier interface. +func (r *RSAPSS) Name() string { + return "RSASSA-PSS" +} + +// Sign generates SHA-256 digest and signs it using RSASSA-PSS. +func (r *RSAPSS) Sign(data []byte) ([]byte, error) { + digest, err := r.hasher.Hash(data) + if err != nil { + return []byte{}, fmt.Errorf("failed to get hash: %w", err) + } + + signature, err := rsa.SignPSS(rand.Reader, &r.privateKey, r.hash, digest, &rsa.PSSOptions{ + SaltLength: rsa.PSSSaltLengthEqualsHash, + Hash: r.hash, + }) + if err != nil { + return nil, fmt.Errorf("failed to sign: %w", err) + } + + return signature, nil +} + +// Verify compares data with signature. +func (r *RSAPSS) Verify(data []byte, signature []byte) error { + digest, err := r.hasher.Hash(data) + if err != nil { + return fmt.Errorf("failed to get hash: %w", err) + } + + err = rsa.VerifyPSS(&r.publicKey, r.hash, digest, signature, &rsa.PSSOptions{ + SaltLength: rsa.PSSSaltLengthEqualsHash, + Hash: r.hash, + }) + if err != nil { + return fmt.Errorf("failed to verify: %w", err) + } + + return nil +} diff --git a/crypto/rsa_test.go b/crypto/rsa_test.go new file mode 100644 index 0000000..3645f35 --- /dev/null +++ b/crypto/rsa_test.go @@ -0,0 +1,91 @@ +package crypto_test + +import ( + "crypto/rand" + "crypto/rsa" + "testing" + + "github.com/stretchr/testify/require" + "github.com/tarantool/go-storage/crypto" +) + +func TestRsaWithoutKeys(t *testing.T) { + t.Parallel() + + rsapss := crypto.NewRSAPSS(rsa.PrivateKey{}, rsa.PublicKey{}) //nolint:exhaustruct + require.NotNil(t, rsapss, "rsapss must be returned") + + data := []byte("abc") + + sig, err := rsapss.Sign(data) + require.ErrorContains(t, err, "failed to sign: crypto/rsa: missing public modulus") + require.Nil(t, sig, "signature must be nil") + + err = rsapss.Verify(data, sig) + require.ErrorContains(t, err, "failed to verify: crypto/rsa: missing public modulus") +} + +func TestRsaOnlyPrivateKey(t *testing.T) { + t.Parallel() + + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) + require.NoError(t, err) + + rsapss := crypto.NewRSAPSS(*privateKey, rsa.PublicKey{}) //nolint:exhaustruct + require.NotNil(t, rsapss, "rsapss must be returned") + + data := []byte("abc") + + sig, err := rsapss.Sign(data) + require.NoError(t, err, "Sign must be successful") + require.NotNil(t, sig, "signature must be returned") + + err = rsapss.Verify(data, sig) + require.ErrorContains(t, err, "failed to verify: crypto/rsa: missing public modulus") +} + +func TestRsaOnlyPublicKey(t *testing.T) { + t.Parallel() + + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) + require.NoError(t, err) + + rsapss := crypto.NewRSAPSS(rsa.PrivateKey{}, privateKey.PublicKey) //nolint:exhaustruct + require.NotNil(t, rsapss, "rsapss must be returned") + + data := []byte("abc") + + sig, err := rsapss.Sign(data) + require.ErrorContains(t, err, "failed to sign: crypto/rsa: missing public modulus") + require.Nil(t, sig, "signature must be nil") + + // Re-create to have a valid sign. + rsapss = crypto.NewRSAPSS(*privateKey, privateKey.PublicKey) + require.NotNil(t, rsapss, "rsapss must be returned") + + sign, err := rsapss.Sign(data) + require.NoError(t, err) + require.NotNil(t, sign) + + err = rsapss.Verify(data, sign) + require.NoError(t, err, "Verify must be successful") +} + +func TestRsaSignVerify(t *testing.T) { + t.Parallel() + + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) + require.NoError(t, err) + + rsapss := crypto.NewRSAPSS(*privateKey, privateKey.PublicKey) + require.NotNil(t, rsapss, "rsapss must be returned") + + data := []byte("abc") + + sig, err := rsapss.Sign(data) + require.NoError(t, err, "Sign must be successful") + require.NotNil(t, sig, "signature must be returned") + + err = rsapss.Verify(data, sig) + require.NoError(t, err, "Verify must be successful") +} diff --git a/go.mod b/go.mod index 490d788..a0b4e2a 100644 --- a/go.mod +++ b/go.mod @@ -5,10 +5,12 @@ go 1.24.0 require ( github.com/gojuno/minimock/v3 v3.4.7 github.com/stretchr/testify v1.11.1 + github.com/tarantool/go-iproto v1.1.0 github.com/tarantool/go-option v1.0.0 - github.com/tarantool/go-tarantool/v2 v2.4.0 + github.com/tarantool/go-tarantool/v2 v2.4.1 github.com/vmihailenco/msgpack/v5 v5.4.1 go.etcd.io/etcd/client/v3 v3.6.5 + gopkg.in/yaml.v3 v3.0.1 ) require ( @@ -17,27 +19,25 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect github.com/hexdigest/gowrap v1.4.3 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/tarantool/go-iproto v1.1.0 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect go.etcd.io/etcd/api/v3 v3.6.5 // indirect go.etcd.io/etcd/client/pkg/v3 v3.6.5 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/mod v0.27.0 // indirect - golang.org/x/net v0.44.0 // indirect + golang.org/x/mod v0.28.0 // indirect + golang.org/x/net v0.46.0 // indirect golang.org/x/sync v0.17.0 // indirect - golang.org/x/sys v0.36.0 // indirect - golang.org/x/text v0.29.0 // indirect - golang.org/x/tools v0.36.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250922171735-9219d122eba9 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250922171735-9219d122eba9 // indirect - google.golang.org/grpc v1.75.1 // indirect - google.golang.org/protobuf v1.36.9 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect + golang.org/x/sys v0.38.0 // indirect + golang.org/x/text v0.30.0 // indirect + golang.org/x/tools v0.37.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251110190251-83f479183930 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251110190251-83f479183930 // indirect + google.golang.org/grpc v1.76.0 // indirect + google.golang.org/protobuf v1.36.10 // indirect ) tool github.com/gojuno/minimock/v3/cmd/minimock diff --git a/go.sum b/go.sum index 13fa7a5..a6a44c1 100644 --- a/go.sum +++ b/go.sum @@ -18,8 +18,8 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4= github.com/hexdigest/gowrap v1.4.3 h1:m+t8aj1pUiFQbEiE8QJg2xdYVH5DAMluLgZ9P/qEF0k= github.com/hexdigest/gowrap v1.4.3/go.mod h1:XWL8oQW2H3fX5ll8oT3Fduh4mt2H3cUAGQHQLMUbmG4= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -40,8 +40,8 @@ github.com/tarantool/go-iproto v1.1.0 h1:HULVOIHsiehI+FnHfM7wMDntuzUddO09DKqu2Wn github.com/tarantool/go-iproto v1.1.0/go.mod h1:LNCtdyZxojUed8SbOiYHoc3v9NvaZTB7p96hUySMlIo= github.com/tarantool/go-option v1.0.0 h1:+Etw0i3TjsXvADTo5rfZNCfsXe3BfHOs+iVfIrl0Nlo= github.com/tarantool/go-option v1.0.0/go.mod h1:lXzzeZtL+rPUtLOCDP6ny3FemFBjruG9aHKzNN2bS08= -github.com/tarantool/go-tarantool/v2 v2.4.0 h1:cfGngxdknpVVbd/vF2LvaoWsKjsLV9i3xC859XgsJlI= -github.com/tarantool/go-tarantool/v2 v2.4.0/go.mod h1:MTbhdjFc3Jl63Lgi/UJr5D+QbT+QegqOzsNJGmaw7VM= +github.com/tarantool/go-tarantool/v2 v2.4.1 h1:Bk9mh+gMPVmHTSefHvVBpEkf6P2UZA/8xa5kqgyQtyo= +github.com/tarantool/go-tarantool/v2 v2.4.1/go.mod h1:MTbhdjFc3Jl63Lgi/UJr5D+QbT+QegqOzsNJGmaw7VM= github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= @@ -77,14 +77,14 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= -golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= +golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= +golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= -golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= +golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= +golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -93,32 +93,32 @@ golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= -golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= -golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= +golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= +golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/api v0.0.0-20250922171735-9219d122eba9 h1:jm6v6kMRpTYKxBRrDkYAitNJegUeO1Mf3Kt80obv0gg= -google.golang.org/genproto/googleapis/api v0.0.0-20250922171735-9219d122eba9/go.mod h1:LmwNphe5Afor5V3R5BppOULHOnt2mCIf+NxMd4XiygE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250922171735-9219d122eba9 h1:V1jCN2HBa8sySkR5vLcCSqJSTMv093Rw9EJefhQGP7M= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250922171735-9219d122eba9/go.mod h1:HSkG/KdJWusxU1F6CNrwNDjBMgisKxGnc5dAZfT0mjQ= -google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI= -google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= -google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= -google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +google.golang.org/genproto/googleapis/api v0.0.0-20251110190251-83f479183930 h1:8BWFtrvJRbplrKV5VHlIm4YM726eeBPPAL2QDNWhRrU= +google.golang.org/genproto/googleapis/api v0.0.0-20251110190251-83f479183930/go.mod h1:G5IanEx8/PgI9w6CFcYQf7jMtHQhZruvfM1i3qOqk5U= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251110190251-83f479183930 h1:tK4fkUnnRhig9TsTp4otV1FxwBFYgbKUq1RY0V6KZ4U= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251110190251-83f479183930/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= +google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/hasher/hasher.go b/hasher/hasher.go index 30bbd2f..925dd71 100644 --- a/hasher/hasher.go +++ b/hasher/hasher.go @@ -41,6 +41,8 @@ func (h *sha256Hasher) Hash(data []byte) ([]byte, error) { return nil, ErrDataIsNil } + h.hash.Reset() + n, err := h.hash.Write(data) if n < len(data) || err != nil { return nil, fmt.Errorf("failed to write data: %w", err) @@ -71,6 +73,8 @@ func (h *sha1Hasher) Hash(data []byte) ([]byte, error) { return nil, ErrDataIsNil } + h.hash.Reset() + n, err := h.hash.Write(data) if n < len(data) || err != nil { return nil, fmt.Errorf("failed to write data: %w", err) diff --git a/marshaller/marshaller.go b/marshaller/marshaller.go new file mode 100644 index 0000000..83702b4 --- /dev/null +++ b/marshaller/marshaller.go @@ -0,0 +1,59 @@ +// Package marshaller represent interface to transformation. +package marshaller + +import ( + "errors" + + "gopkg.in/yaml.v3" +) + +// ErrMarshall is returned if Marshalling failed. +var ErrMarshall = errors.New("failed to marshal") + +// ErrUnmarshall is returned if Unmarshalling failed. +var ErrUnmarshall = errors.New("failed to unmarshal") + +// DefaultMarshaller - serialization by default (JSON/Protobuf/etc), +// implements one time for all objects. +// Required for `integrity.Storage` to set marshalling format for any type object +// and as recommendation for developers of `Storage` wrappers. +type DefaultMarshaller interface { + Marshal(data any) ([]byte, error) + Unmarshal(data []byte, out any) error +} + +// Marshallable - custom object serialization, implements for each object. +// Required for `integrity.Storage` type to set marshalling format to specific object +// and as recommendation for developers of `Storage` wrappers. +type Marshallable interface { + Marshal() ([]byte, error) + Unmarshal(data []byte, out any) error +} + +// YAMLMarshaller struct represent realization. +type YAMLMarshaller struct{} + +// NewYamlMarshaller creates new NewYamlMarshaller object. +func NewYamlMarshaller() YAMLMarshaller { + return YAMLMarshaller{} +} + +// Marshal implements interface. +func (m YAMLMarshaller) Marshal(data any) ([]byte, error) { + marshalled, err := yaml.Marshal(data) + if err != nil { + return []byte{}, ErrMarshall + } + + return marshalled, nil +} + +// Unmarshal implements interface. +func (m YAMLMarshaller) Unmarshal(data []byte, out any) error { + err := yaml.Unmarshal(data, &out) + if err != nil { + return ErrUnmarshall + } + + return nil +} diff --git a/marshaller/marshaller_test.go b/marshaller/marshaller_test.go new file mode 100644 index 0000000..fdf7fb6 --- /dev/null +++ b/marshaller/marshaller_test.go @@ -0,0 +1,59 @@ +package marshaller_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/tarantool/go-storage/marshaller" +) + +type YamlStruct struct { + Title string `yaml:"title"` + Link string `yaml:"link"` +} + +func TestMarshal(t *testing.T) { + t.Parallel() + + defer func() { + if r := recover(); r == nil { + t.Errorf("The test did not panic") + } + }() + + marshaller := marshaller.NewYamlMarshaller() + + data, err := marshaller.Marshal([]byte{123, 123}) + require.NoError(t, err) + require.NotNil(t, data) + + ch := make(chan int) + + data, err = marshaller.Marshal(ch) + require.Error(t, err) + require.Nil(t, data) +} + +func TestUnmarshal(t *testing.T) { + t.Parallel() + + marshaller := marshaller.NewYamlMarshaller() + + var unmarshaledYaml YamlStruct + + validYaml := ` +title: "Link" +link: "https://some.link" +` + + err := marshaller.Unmarshal([]byte(validYaml), &unmarshaledYaml) + require.NoError(t, err) + + invalidYaml := ` +TITLE: 123 + Link: true +` + + err = marshaller.Unmarshal([]byte(invalidYaml), &unmarshaledYaml) + require.Error(t, err) +}